element.rs

    1use crate::{
    2    ActiveDiagnostic, BUFFER_HEADER_PADDING, BlockId, CURSORS_VISIBLE_FOR, ChunkRendererContext,
    3    ChunkReplacement, CodeActionSource, ColumnarMode, ConflictsOurs, ConflictsOursMarker,
    4    ConflictsOuter, ConflictsTheirs, ConflictsTheirsMarker, ContextMenuPlacement, CursorShape,
    5    CustomBlockId, DisplayDiffHunk, DisplayPoint, DisplayRow, EditDisplayMode, EditPrediction,
    6    Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, FILE_HEADER_HEIGHT,
    7    FocusedBlock, GutterDimensions, GutterHoverButton, HalfPageDown, HalfPageUp, HandleInput,
    8    HoveredCursor, InlayHintRefreshReason, JumpData, LineDown, LineHighlight, LineUp, MAX_LINE_LEN,
    9    MINIMAP_FONT_SIZE, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts, PageDown, PageUp,
   10    PhantomDiffReviewIndicator, Point, RowExt, RowRangeExt, SelectPhase, Selection,
   11    SelectionDragState, SelectionEffects, SizingBehavior, SoftWrap, StickyHeaderExcerpt, ToPoint,
   12    ToggleFold, ToggleFoldAll,
   13    code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
   14    column_pixels,
   15    display_map::{
   16        Block, BlockContext, BlockStyle, ChunkRendererId, DisplaySnapshot, EditorMargins,
   17        HighlightKey, HighlightedChunk, ToDisplayPoint,
   18    },
   19    editor_settings::{
   20        CurrentLineHighlight, DocumentColorsRenderMode, DoubleClickInMultibuffer, Minimap,
   21        MinimapThumb, MinimapThumbBorder, ScrollBeyondLastLine, ScrollbarAxes,
   22        ScrollbarDiagnostics, ShowMinimap,
   23    },
   24    git::blame::{BlameRenderer, GitBlame, GlobalBlameRenderer},
   25    hover_popover::{
   26        self, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
   27        POPOVER_RIGHT_OFFSET, hover_at,
   28    },
   29    inlay_hint_settings,
   30    mouse_context_menu::{self, MenuPosition},
   31    scroll::{
   32        ActiveScrollbarState, Autoscroll, ScrollOffset, ScrollPixelOffset, ScrollbarThumbState,
   33        scroll_amount::ScrollAmount,
   34    },
   35};
   36use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
   37use collections::{BTreeMap, HashMap, HashSet};
   38use feature_flags::{DiffReviewFeatureFlag, FeatureFlagAppExt as _};
   39use file_icons::FileIcons;
   40use git::{Oid, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
   41use gpui::{
   42    Action, Along, AnyElement, App, AppContext, AvailableSpace, Axis as ScrollbarAxis, BorderStyle,
   43    Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corners, CursorStyle, DispatchPhase,
   44    Edges, Element, ElementInputHandler, Entity, Focusable as _, Font, FontId, FontWeight,
   45    GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero, Length,
   46    Modifiers, ModifiersChangedEvent, MouseButton, MouseClickEvent, MouseDownEvent, MouseMoveEvent,
   47    MousePressureEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, PressureStage, ScrollDelta,
   48    ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement,
   49    Style, Styled, StyledText, TextAlign, TextRun, TextStyleRefinement, WeakEntity, Window,
   50    anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, pattern_slash,
   51    point, px, quad, relative, size, solid_background, transparent_black,
   52};
   53use itertools::Itertools;
   54use language::{
   55    HighlightedText, IndentGuideSettings, LanguageAwareStyling,
   56    language_settings::ShowWhitespaceSetting,
   57};
   58use markdown::Markdown;
   59use multi_buffer::{
   60    Anchor, ExcerptBoundaryInfo, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint,
   61    MultiBufferRow, RowInfo,
   62};
   63
   64use project::{
   65    DisableAiSettings, Entry,
   66    debugger::breakpoint_store::{Breakpoint, BreakpointSessionState},
   67    project_settings::ProjectSettings,
   68};
   69use settings::{
   70    GitGutterSetting, GitHunkStyleSetting, IndentGuideBackgroundColoring, IndentGuideColoring,
   71    RelativeLineNumbers, Settings,
   72};
   73use smallvec::{SmallVec, smallvec};
   74use std::{
   75    any::TypeId,
   76    borrow::Cow,
   77    cell::Cell,
   78    cmp::{self, Ordering},
   79    fmt::{self, Write},
   80    iter, mem,
   81    ops::{Deref, Range},
   82    path::{self, Path},
   83    rc::Rc,
   84    sync::Arc,
   85    time::{Duration, Instant},
   86};
   87use sum_tree::Bias;
   88use text::{BufferId, SelectionGoal};
   89use theme::{ActiveTheme, Appearance, PlayerColor};
   90use theme_settings::BufferLineHeight;
   91use ui::utils::ensure_minimum_contrast;
   92use ui::{
   93    ButtonLike, ContextMenu, Indicator, KeyBinding, POPOVER_Y_PADDING, Tooltip, prelude::*,
   94    right_click_menu, scrollbars::ShowScrollbar, text_for_keystroke,
   95};
   96use unicode_segmentation::UnicodeSegmentation;
   97use util::post_inc;
   98use util::{RangeExt, ResultExt, debug_panic};
   99use workspace::{
  100    CollaboratorId, ItemHandle, ItemSettings, OpenInTerminal, OpenTerminal, RevealInProjectPanel,
  101    Workspace,
  102    item::{Item, ItemBufferKind},
  103};
  104
  105/// Determines what kinds of highlights should be applied to a lines background.
  106#[derive(Clone, Copy, Default)]
  107struct LineHighlightSpec {
  108    selection: bool,
  109    breakpoint: bool,
  110    _active_stack_frame: bool,
  111}
  112
  113#[derive(Debug)]
  114struct SelectionLayout {
  115    head: DisplayPoint,
  116    cursor_shape: CursorShape,
  117    is_newest: bool,
  118    is_local: bool,
  119    range: Range<DisplayPoint>,
  120    active_rows: Range<DisplayRow>,
  121    user_name: Option<SharedString>,
  122}
  123
  124struct InlineBlameLayout {
  125    element: AnyElement,
  126    bounds: Bounds<Pixels>,
  127    buffer_id: BufferId,
  128    entry: BlameEntry,
  129}
  130
  131impl SelectionLayout {
  132    fn new<T: ToPoint + ToDisplayPoint + Clone>(
  133        selection: Selection<T>,
  134        line_mode: bool,
  135        cursor_offset: bool,
  136        cursor_shape: CursorShape,
  137        map: &DisplaySnapshot,
  138        is_newest: bool,
  139        is_local: bool,
  140        user_name: Option<SharedString>,
  141    ) -> Self {
  142        let point_selection = selection.map(|p| p.to_point(map.buffer_snapshot()));
  143        let display_selection = point_selection.map(|p| p.to_display_point(map));
  144        let mut range = display_selection.range();
  145        let mut head = display_selection.head();
  146        let mut active_rows = map.prev_line_boundary(point_selection.start).1.row()
  147            ..map.next_line_boundary(point_selection.end).1.row();
  148
  149        // vim visual line mode
  150        if line_mode {
  151            let point_range = map.expand_to_line(point_selection.range());
  152            range = point_range.start.to_display_point(map)..point_range.end.to_display_point(map);
  153        }
  154
  155        // any vim visual mode (including line mode)
  156        if cursor_offset && !range.is_empty() && !selection.reversed {
  157            if head.column() > 0 {
  158                head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left);
  159            } else if head.row().0 > 0 && head != map.max_point() {
  160                head = map.clip_point(
  161                    DisplayPoint::new(
  162                        head.row().previous_row(),
  163                        map.line_len(head.row().previous_row()),
  164                    ),
  165                    Bias::Left,
  166                );
  167                // updating range.end is a no-op unless you're cursor is
  168                // on the newline containing a multi-buffer divider
  169                // in which case the clip_point may have moved the head up
  170                // an additional row.
  171                range.end = DisplayPoint::new(head.row().next_row(), 0);
  172                active_rows.end = head.row();
  173            }
  174        }
  175
  176        Self {
  177            head,
  178            cursor_shape,
  179            is_newest,
  180            is_local,
  181            range,
  182            active_rows,
  183            user_name,
  184        }
  185    }
  186}
  187
  188#[derive(Default)]
  189struct RenderBlocksOutput {
  190    // We store spacer blocks separately because they paint in a different order
  191    // (spacers -> indent guides -> non-spacers)
  192    non_spacer_blocks: Vec<BlockLayout>,
  193    spacer_blocks: Vec<BlockLayout>,
  194    row_block_types: HashMap<DisplayRow, bool>,
  195    resized_blocks: Option<HashMap<CustomBlockId, u32>>,
  196}
  197
  198pub struct EditorElement {
  199    editor: Entity<Editor>,
  200    style: EditorStyle,
  201    split_side: Option<SplitSide>,
  202}
  203
  204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
  205pub enum SplitSide {
  206    Left,
  207    Right,
  208}
  209
  210impl EditorElement {
  211    pub(crate) const SCROLLBAR_WIDTH: Pixels = px(15.);
  212
  213    pub fn new(editor: &Entity<Editor>, style: EditorStyle) -> Self {
  214        Self {
  215            editor: editor.clone(),
  216            style,
  217            split_side: None,
  218        }
  219    }
  220
  221    pub fn set_split_side(&mut self, side: SplitSide) {
  222        self.split_side = Some(side);
  223    }
  224
  225    fn should_show_buffer_headers(&self) -> bool {
  226        self.split_side.is_none()
  227    }
  228
  229    fn register_actions(&self, window: &mut Window, cx: &mut App) {
  230        let editor = &self.editor;
  231        editor.update(cx, |editor, cx| {
  232            for action in editor.editor_actions.borrow().values() {
  233                (action)(editor, window, cx)
  234            }
  235        });
  236
  237        crate::rust_analyzer_ext::apply_related_actions(editor, window, cx);
  238        crate::clangd_ext::apply_related_actions(editor, window, cx);
  239
  240        register_action(editor, window, Editor::open_context_menu);
  241        register_action(editor, window, Editor::move_left);
  242        register_action(editor, window, Editor::move_right);
  243        register_action(editor, window, Editor::move_down);
  244        register_action(editor, window, Editor::move_down_by_lines);
  245        register_action(editor, window, Editor::select_down_by_lines);
  246        register_action(editor, window, Editor::move_up);
  247        register_action(editor, window, Editor::move_up_by_lines);
  248        register_action(editor, window, Editor::select_up_by_lines);
  249        register_action(editor, window, Editor::select_page_down);
  250        register_action(editor, window, Editor::select_page_up);
  251        register_action(editor, window, Editor::cancel);
  252        register_action(editor, window, Editor::newline);
  253        register_action(editor, window, Editor::newline_above);
  254        register_action(editor, window, Editor::newline_below);
  255        register_action(editor, window, Editor::backspace);
  256        register_action(editor, window, Editor::blame_hover);
  257        register_action(editor, window, Editor::delete);
  258        register_action(editor, window, Editor::tab);
  259        register_action(editor, window, Editor::next_snippet_tabstop);
  260        register_action(editor, window, Editor::previous_snippet_tabstop);
  261        register_action(editor, window, Editor::backtab);
  262        register_action(editor, window, Editor::indent);
  263        register_action(editor, window, Editor::outdent);
  264        register_action(editor, window, Editor::autoindent);
  265        register_action(editor, window, Editor::delete_line);
  266        register_action(editor, window, Editor::join_lines);
  267        register_action(editor, window, Editor::sort_lines_by_length);
  268        register_action(editor, window, Editor::sort_lines_case_sensitive);
  269        register_action(editor, window, Editor::sort_lines_case_insensitive);
  270        register_action(editor, window, Editor::reverse_lines);
  271        register_action(editor, window, Editor::shuffle_lines);
  272        register_action(editor, window, Editor::rotate_selections_forward);
  273        register_action(editor, window, Editor::rotate_selections_backward);
  274        register_action(editor, window, Editor::convert_indentation_to_spaces);
  275        register_action(editor, window, Editor::convert_indentation_to_tabs);
  276        register_action(editor, window, Editor::convert_to_upper_case);
  277        register_action(editor, window, Editor::convert_to_lower_case);
  278        register_action(editor, window, Editor::convert_to_title_case);
  279        register_action(editor, window, Editor::convert_to_snake_case);
  280        register_action(editor, window, Editor::convert_to_kebab_case);
  281        register_action(editor, window, Editor::convert_to_upper_camel_case);
  282        register_action(editor, window, Editor::convert_to_lower_camel_case);
  283        register_action(editor, window, Editor::convert_to_opposite_case);
  284        register_action(editor, window, Editor::convert_to_sentence_case);
  285        register_action(editor, window, Editor::toggle_case);
  286        register_action(editor, window, Editor::convert_to_rot13);
  287        register_action(editor, window, Editor::convert_to_rot47);
  288        register_action(editor, window, Editor::delete_to_previous_word_start);
  289        register_action(editor, window, Editor::delete_to_previous_subword_start);
  290        register_action(editor, window, Editor::delete_to_next_word_end);
  291        register_action(editor, window, Editor::delete_to_next_subword_end);
  292        register_action(editor, window, Editor::delete_to_beginning_of_line);
  293        register_action(editor, window, Editor::delete_to_end_of_line);
  294        register_action(editor, window, Editor::cut_to_end_of_line);
  295        register_action(editor, window, Editor::duplicate_line_up);
  296        register_action(editor, window, Editor::duplicate_line_down);
  297        register_action(editor, window, Editor::duplicate_selection);
  298        register_action(editor, window, Editor::move_line_up);
  299        register_action(editor, window, Editor::move_line_down);
  300        register_action(editor, window, Editor::transpose);
  301        register_action(editor, window, Editor::rewrap);
  302        register_action(editor, window, Editor::cut);
  303        register_action(editor, window, Editor::kill_ring_cut);
  304        register_action(editor, window, Editor::kill_ring_yank);
  305        register_action(editor, window, Editor::copy);
  306        register_action(editor, window, Editor::copy_and_trim);
  307        register_action(editor, window, Editor::diff_clipboard_with_selection);
  308        register_action(editor, window, Editor::paste);
  309        register_action(editor, window, Editor::undo);
  310        register_action(editor, window, Editor::redo);
  311        register_action(editor, window, Editor::move_page_up);
  312        register_action(editor, window, Editor::move_page_down);
  313        register_action(editor, window, Editor::next_screen);
  314        register_action(editor, window, Editor::scroll_cursor_top);
  315        register_action(editor, window, Editor::scroll_cursor_center);
  316        register_action(editor, window, Editor::scroll_cursor_bottom);
  317        register_action(editor, window, Editor::scroll_cursor_center_top_bottom);
  318        register_action(editor, window, |editor, _: &LineDown, window, cx| {
  319            editor.scroll_screen(&ScrollAmount::Line(1.), window, cx)
  320        });
  321        register_action(editor, window, |editor, _: &LineUp, window, cx| {
  322            editor.scroll_screen(&ScrollAmount::Line(-1.), window, cx)
  323        });
  324        register_action(editor, window, |editor, _: &HalfPageDown, window, cx| {
  325            editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx)
  326        });
  327        register_action(
  328            editor,
  329            window,
  330            |editor, HandleInput(text): &HandleInput, window, cx| {
  331                if text.is_empty() {
  332                    return;
  333                }
  334                editor.handle_input(text, window, cx);
  335            },
  336        );
  337        register_action(editor, window, |editor, _: &HalfPageUp, window, cx| {
  338            editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx)
  339        });
  340        register_action(editor, window, |editor, _: &PageDown, window, cx| {
  341            editor.scroll_screen(&ScrollAmount::Page(1.), window, cx)
  342        });
  343        register_action(editor, window, |editor, _: &PageUp, window, cx| {
  344            editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx)
  345        });
  346        register_action(editor, window, Editor::move_to_previous_word_start);
  347        register_action(editor, window, Editor::move_to_previous_subword_start);
  348        register_action(editor, window, Editor::move_to_next_word_end);
  349        register_action(editor, window, Editor::move_to_next_subword_end);
  350        register_action(editor, window, Editor::move_to_beginning_of_line);
  351        register_action(editor, window, Editor::move_to_end_of_line);
  352        register_action(editor, window, Editor::move_to_start_of_paragraph);
  353        register_action(editor, window, Editor::move_to_end_of_paragraph);
  354        register_action(editor, window, Editor::move_to_beginning);
  355        register_action(editor, window, Editor::move_to_end);
  356        register_action(editor, window, Editor::move_to_start_of_excerpt);
  357        register_action(editor, window, Editor::move_to_start_of_next_excerpt);
  358        register_action(editor, window, Editor::move_to_end_of_excerpt);
  359        register_action(editor, window, Editor::move_to_end_of_previous_excerpt);
  360        register_action(editor, window, Editor::select_up);
  361        register_action(editor, window, Editor::select_down);
  362        register_action(editor, window, Editor::select_left);
  363        register_action(editor, window, Editor::select_right);
  364        register_action(editor, window, Editor::select_to_previous_word_start);
  365        register_action(editor, window, Editor::select_to_previous_subword_start);
  366        register_action(editor, window, Editor::select_to_next_word_end);
  367        register_action(editor, window, Editor::select_to_next_subword_end);
  368        register_action(editor, window, Editor::select_to_beginning_of_line);
  369        register_action(editor, window, Editor::select_to_end_of_line);
  370        register_action(editor, window, Editor::select_to_start_of_paragraph);
  371        register_action(editor, window, Editor::select_to_end_of_paragraph);
  372        register_action(editor, window, Editor::select_to_start_of_excerpt);
  373        register_action(editor, window, Editor::select_to_start_of_next_excerpt);
  374        register_action(editor, window, Editor::select_to_end_of_excerpt);
  375        register_action(editor, window, Editor::select_to_end_of_previous_excerpt);
  376        register_action(editor, window, Editor::select_to_beginning);
  377        register_action(editor, window, Editor::select_to_end);
  378        register_action(editor, window, Editor::select_all);
  379        register_action(editor, window, |editor, action, window, cx| {
  380            editor.select_all_matches(action, window, cx).log_err();
  381        });
  382        register_action(editor, window, Editor::select_line);
  383        register_action(editor, window, Editor::split_selection_into_lines);
  384        register_action(editor, window, Editor::add_selection_above);
  385        register_action(editor, window, Editor::add_selection_below);
  386        register_action(editor, window, Editor::insert_snippet_at_selections);
  387        register_action(editor, window, |editor, action, window, cx| {
  388            editor.select_next(action, window, cx).log_err();
  389        });
  390        register_action(editor, window, |editor, action, window, cx| {
  391            editor.select_previous(action, window, cx).log_err();
  392        });
  393        register_action(editor, window, |editor, action, window, cx| {
  394            editor.find_next_match(action, window, cx).log_err();
  395        });
  396        register_action(editor, window, |editor, action, window, cx| {
  397            editor.find_previous_match(action, window, cx).log_err();
  398        });
  399        register_action(editor, window, Editor::toggle_comments);
  400        register_action(editor, window, Editor::toggle_block_comments);
  401        register_action(editor, window, Editor::select_larger_syntax_node);
  402        register_action(editor, window, Editor::select_smaller_syntax_node);
  403        register_action(editor, window, Editor::select_next_syntax_node);
  404        register_action(editor, window, Editor::select_prev_syntax_node);
  405        register_action(
  406            editor,
  407            window,
  408            Editor::select_to_start_of_larger_syntax_node,
  409        );
  410        register_action(editor, window, Editor::select_to_end_of_larger_syntax_node);
  411        register_action(editor, window, Editor::unwrap_syntax_node);
  412        register_action(editor, window, Editor::move_to_start_of_larger_syntax_node);
  413        register_action(editor, window, Editor::move_to_end_of_larger_syntax_node);
  414        register_action(editor, window, Editor::select_enclosing_symbol);
  415        register_action(editor, window, Editor::move_to_enclosing_bracket);
  416        register_action(editor, window, Editor::undo_selection);
  417        register_action(editor, window, Editor::redo_selection);
  418        if editor.read(cx).buffer_kind(cx) == ItemBufferKind::Multibuffer {
  419            register_action(editor, window, Editor::expand_excerpts);
  420            register_action(editor, window, Editor::expand_excerpts_up);
  421            register_action(editor, window, Editor::expand_excerpts_down);
  422        }
  423        register_action(editor, window, Editor::go_to_diagnostic);
  424        register_action(editor, window, Editor::go_to_prev_diagnostic);
  425        register_action(editor, window, Editor::go_to_next_hunk);
  426        register_action(editor, window, Editor::go_to_prev_hunk);
  427        register_action(editor, window, Editor::go_to_next_document_highlight);
  428        register_action(editor, window, Editor::go_to_prev_document_highlight);
  429        register_action(editor, window, |editor, action, window, cx| {
  430            editor
  431                .go_to_definition(action, window, cx)
  432                .detach_and_log_err(cx);
  433        });
  434        register_action(editor, window, |editor, action, window, cx| {
  435            editor
  436                .go_to_definition_split(action, window, cx)
  437                .detach_and_log_err(cx);
  438        });
  439        register_action(editor, window, |editor, action, window, cx| {
  440            editor
  441                .go_to_declaration(action, window, cx)
  442                .detach_and_log_err(cx);
  443        });
  444        register_action(editor, window, |editor, action, window, cx| {
  445            editor
  446                .go_to_declaration_split(action, window, cx)
  447                .detach_and_log_err(cx);
  448        });
  449        register_action(editor, window, |editor, action, window, cx| {
  450            editor
  451                .go_to_implementation(action, window, cx)
  452                .detach_and_log_err(cx);
  453        });
  454        register_action(editor, window, |editor, action, window, cx| {
  455            editor
  456                .go_to_implementation_split(action, window, cx)
  457                .detach_and_log_err(cx);
  458        });
  459        register_action(editor, window, |editor, action, window, cx| {
  460            editor
  461                .go_to_type_definition(action, window, cx)
  462                .detach_and_log_err(cx);
  463        });
  464        register_action(editor, window, |editor, action, window, cx| {
  465            editor
  466                .go_to_type_definition_split(action, window, cx)
  467                .detach_and_log_err(cx);
  468        });
  469        register_action(editor, window, Editor::open_url);
  470        register_action(editor, window, Editor::open_selected_filename);
  471        register_action(editor, window, Editor::fold);
  472        register_action(editor, window, Editor::fold_at_level);
  473        register_action(editor, window, Editor::fold_at_level_1);
  474        register_action(editor, window, Editor::fold_at_level_2);
  475        register_action(editor, window, Editor::fold_at_level_3);
  476        register_action(editor, window, Editor::fold_at_level_4);
  477        register_action(editor, window, Editor::fold_at_level_5);
  478        register_action(editor, window, Editor::fold_at_level_6);
  479        register_action(editor, window, Editor::fold_at_level_7);
  480        register_action(editor, window, Editor::fold_at_level_8);
  481        register_action(editor, window, Editor::fold_at_level_9);
  482        register_action(editor, window, Editor::fold_all);
  483        register_action(editor, window, Editor::fold_function_bodies);
  484        register_action(editor, window, Editor::fold_recursive);
  485        register_action(editor, window, Editor::toggle_fold);
  486        register_action(editor, window, Editor::toggle_fold_recursive);
  487        register_action(editor, window, Editor::toggle_fold_all);
  488        register_action(editor, window, Editor::unfold_lines);
  489        register_action(editor, window, Editor::unfold_recursive);
  490        register_action(editor, window, Editor::unfold_all);
  491        register_action(editor, window, Editor::fold_selected_ranges);
  492        register_action(editor, window, Editor::set_mark);
  493        register_action(editor, window, Editor::swap_selection_ends);
  494        register_action(editor, window, Editor::show_completions);
  495        register_action(editor, window, Editor::show_word_completions);
  496        register_action(editor, window, Editor::toggle_code_actions);
  497        register_action(editor, window, Editor::open_excerpts);
  498        register_action(editor, window, Editor::open_excerpts_in_split);
  499        register_action(editor, window, Editor::toggle_soft_wrap);
  500        register_action(editor, window, Editor::toggle_tab_bar);
  501        register_action(editor, window, Editor::toggle_line_numbers);
  502        register_action(editor, window, Editor::toggle_relative_line_numbers);
  503        register_action(editor, window, Editor::toggle_indent_guides);
  504        register_action(editor, window, Editor::toggle_inlay_hints);
  505        register_action(editor, window, Editor::toggle_code_lens_action);
  506        register_action(editor, window, Editor::toggle_semantic_highlights);
  507        register_action(editor, window, Editor::toggle_edit_predictions);
  508        if editor.read(cx).diagnostics_enabled() {
  509            register_action(editor, window, Editor::toggle_diagnostics);
  510        }
  511        if editor.read(cx).inline_diagnostics_enabled() {
  512            register_action(editor, window, Editor::toggle_inline_diagnostics);
  513        }
  514        if editor.read(cx).supports_minimap(cx) {
  515            register_action(editor, window, Editor::toggle_minimap);
  516        }
  517        register_action(editor, window, hover_popover::hover);
  518        register_action(editor, window, Editor::reveal_in_finder);
  519        register_action(editor, window, Editor::copy_path);
  520        register_action(editor, window, Editor::copy_relative_path);
  521        register_action(editor, window, Editor::copy_file_name);
  522        register_action(editor, window, Editor::copy_file_name_without_extension);
  523        register_action(editor, window, Editor::copy_highlight_json);
  524        register_action(editor, window, Editor::copy_permalink_to_line);
  525        register_action(editor, window, Editor::open_permalink_to_line);
  526        register_action(editor, window, Editor::copy_file_location);
  527        register_action(editor, window, Editor::toggle_git_blame);
  528        register_action(editor, window, Editor::toggle_git_blame_inline);
  529        register_action(editor, window, Editor::open_git_blame_commit);
  530        register_action(editor, window, Editor::toggle_selected_diff_hunks);
  531        register_action(editor, window, Editor::toggle_staged_selected_diff_hunks);
  532        register_action(editor, window, Editor::stage_and_next);
  533        register_action(editor, window, Editor::unstage_and_next);
  534        register_action(editor, window, Editor::expand_all_diff_hunks);
  535        register_action(editor, window, Editor::collapse_all_diff_hunks);
  536        register_action(editor, window, Editor::toggle_review_comments_expanded);
  537        register_action(editor, window, Editor::submit_diff_review_comment_action);
  538        register_action(editor, window, Editor::edit_review_comment);
  539        register_action(editor, window, Editor::delete_review_comment);
  540        register_action(editor, window, Editor::confirm_edit_review_comment_action);
  541        register_action(editor, window, Editor::cancel_edit_review_comment_action);
  542        register_action(editor, window, Editor::go_to_previous_change);
  543        register_action(editor, window, Editor::go_to_next_change);
  544        register_action(editor, window, Editor::go_to_prev_reference);
  545        register_action(editor, window, Editor::go_to_next_reference);
  546        register_action(editor, window, Editor::go_to_previous_symbol);
  547        register_action(editor, window, Editor::go_to_next_symbol);
  548
  549        register_action(editor, window, |editor, action, window, cx| {
  550            if let Some(task) = editor.format(action, window, cx) {
  551                editor.detach_and_notify_err(task, window, cx);
  552            } else {
  553                cx.propagate();
  554            }
  555        });
  556        if editor.read(cx).can_format_selections(cx) {
  557            register_action(editor, window, |editor, action, window, cx| {
  558                if let Some(task) = editor.format_selections(action, window, cx) {
  559                    editor.detach_and_notify_err(task, window, cx);
  560                } else {
  561                    cx.propagate();
  562                }
  563            });
  564        }
  565        register_action(editor, window, |editor, action, window, cx| {
  566            if let Some(task) = editor.organize_imports(action, window, cx) {
  567                editor.detach_and_notify_err(task, window, cx);
  568            } else {
  569                cx.propagate();
  570            }
  571        });
  572        register_action(editor, window, Editor::restart_language_server);
  573        register_action(editor, window, Editor::stop_language_server);
  574        register_action(editor, window, Editor::show_character_palette);
  575        register_action(editor, window, |editor, action, window, cx| {
  576            if let Some(task) = editor.confirm_completion(action, window, cx) {
  577                editor.detach_and_notify_err(task, window, cx);
  578            } else {
  579                cx.propagate();
  580            }
  581        });
  582        register_action(editor, window, |editor, action, window, cx| {
  583            if let Some(task) = editor.confirm_completion_replace(action, window, cx) {
  584                editor.detach_and_notify_err(task, window, cx);
  585            } else {
  586                cx.propagate();
  587            }
  588        });
  589        register_action(editor, window, |editor, action, window, cx| {
  590            if let Some(task) = editor.confirm_completion_insert(action, window, cx) {
  591                editor.detach_and_notify_err(task, window, cx);
  592            } else {
  593                cx.propagate();
  594            }
  595        });
  596        register_action(editor, window, |editor, action, window, cx| {
  597            if let Some(task) = editor.compose_completion(action, window, cx) {
  598                editor.detach_and_notify_err(task, window, cx);
  599            } else {
  600                cx.propagate();
  601            }
  602        });
  603        register_action(editor, window, |editor, action, window, cx| {
  604            if let Some(task) = editor.confirm_code_action(action, window, cx) {
  605                editor.detach_and_notify_err(task, window, cx);
  606            } else {
  607                cx.propagate();
  608            }
  609        });
  610        register_action(editor, window, |editor, action, window, cx| {
  611            if let Some(task) = editor.rename(action, window, cx) {
  612                editor.detach_and_notify_err(task, window, cx);
  613            } else {
  614                cx.propagate();
  615            }
  616        });
  617        register_action(editor, window, |editor, action, window, cx| {
  618            if let Some(task) = editor.confirm_rename(action, window, cx) {
  619                editor.detach_and_notify_err(task, window, cx);
  620            } else {
  621                cx.propagate();
  622            }
  623        });
  624        register_action(editor, window, |editor, action, window, cx| {
  625            if let Some(task) = editor.find_all_references(action, window, cx) {
  626                task.detach_and_log_err(cx);
  627            } else {
  628                cx.propagate();
  629            }
  630        });
  631        register_action(editor, window, Editor::show_signature_help);
  632        register_action(editor, window, Editor::signature_help_prev);
  633        register_action(editor, window, Editor::signature_help_next);
  634        register_action(editor, window, Editor::show_edit_prediction);
  635        register_action(editor, window, Editor::context_menu_first);
  636        register_action(editor, window, Editor::context_menu_prev);
  637        register_action(editor, window, Editor::context_menu_next);
  638        register_action(editor, window, Editor::context_menu_last);
  639        register_action(editor, window, Editor::display_cursor_names);
  640        register_action(editor, window, Editor::unique_lines_case_insensitive);
  641        register_action(editor, window, Editor::unique_lines_case_sensitive);
  642        register_action(editor, window, Editor::accept_next_word_edit_prediction);
  643        register_action(editor, window, Editor::accept_next_line_edit_prediction);
  644        register_action(editor, window, Editor::accept_edit_prediction);
  645        register_action(editor, window, Editor::restore_file);
  646        register_action(editor, window, Editor::git_restore);
  647        register_action(editor, window, Editor::restore_and_next);
  648        register_action(editor, window, Editor::apply_all_diff_hunks);
  649        register_action(editor, window, Editor::apply_selected_diff_hunks);
  650        register_action(editor, window, Editor::open_active_item_in_terminal);
  651        register_action(editor, window, Editor::reload_file);
  652        register_action(editor, window, Editor::spawn_nearest_task);
  653        register_action(editor, window, Editor::insert_uuid_v4);
  654        register_action(editor, window, Editor::insert_uuid_v7);
  655        register_action(editor, window, Editor::open_selections_in_multibuffer);
  656        register_action(editor, window, Editor::toggle_bookmark);
  657        register_action(editor, window, Editor::go_to_next_bookmark);
  658        register_action(editor, window, Editor::go_to_previous_bookmark);
  659        register_action(editor, window, Editor::toggle_breakpoint);
  660        register_action(editor, window, Editor::edit_log_breakpoint);
  661        register_action(editor, window, Editor::enable_breakpoint);
  662        register_action(editor, window, Editor::disable_breakpoint);
  663        register_action(editor, window, Editor::toggle_read_only);
  664        register_action(editor, window, Editor::align_selections);
  665        if editor.read(cx).enable_wrap_selections_in_tag(cx) {
  666            register_action(editor, window, Editor::wrap_selections_in_tag);
  667        }
  668    }
  669
  670    fn register_key_listeners(&self, window: &mut Window, _: &mut App, layout: &EditorLayout) {
  671        let position_map = layout.position_map.clone();
  672        window.on_key_event({
  673            let editor = self.editor.clone();
  674            move |event: &ModifiersChangedEvent, phase, window, cx| {
  675                if phase != DispatchPhase::Bubble {
  676                    return;
  677                }
  678                editor.update(cx, |editor, cx| {
  679                    let inlay_hint_settings = inlay_hint_settings(
  680                        editor.selections.newest_anchor().head(),
  681                        &editor.buffer.read(cx).snapshot(cx),
  682                        cx,
  683                    );
  684
  685                    if let Some(inlay_modifiers) = inlay_hint_settings
  686                        .toggle_on_modifiers_press
  687                        .as_ref()
  688                        .filter(|modifiers| modifiers.modified())
  689                    {
  690                        editor.refresh_inlay_hints(
  691                            InlayHintRefreshReason::ModifiersChanged(
  692                                inlay_modifiers == &event.modifiers,
  693                            ),
  694                            cx,
  695                        );
  696                    }
  697
  698                    if editor.hover_state.focused(window, cx) {
  699                        return;
  700                    }
  701
  702                    editor.handle_modifiers_changed(event.modifiers, &position_map, window, cx);
  703                })
  704            }
  705        });
  706    }
  707
  708    fn mouse_left_down(
  709        editor: &mut Editor,
  710        event: &MouseDownEvent,
  711        position_map: &PositionMap,
  712        line_numbers: &HashMap<MultiBufferRow, LineNumberLayout>,
  713        window: &mut Window,
  714        cx: &mut Context<Editor>,
  715    ) {
  716        if window.default_prevented() {
  717            return;
  718        }
  719
  720        let text_hitbox = &position_map.text_hitbox;
  721        let gutter_hitbox = &position_map.gutter_hitbox;
  722        let point_for_position = position_map.point_for_position(event.position);
  723        let mut click_count = event.click_count;
  724        let mut modifiers = event.modifiers;
  725
  726        if let Some(hovered_hunk) =
  727            position_map
  728                .display_hunks
  729                .iter()
  730                .find_map(|(hunk, hunk_hitbox)| match hunk {
  731                    DisplayDiffHunk::Folded { .. } => None,
  732                    DisplayDiffHunk::Unfolded {
  733                        multi_buffer_range, ..
  734                    } => hunk_hitbox
  735                        .as_ref()
  736                        .is_some_and(|hitbox| hitbox.is_hovered(window))
  737                        .then(|| multi_buffer_range.clone()),
  738                })
  739        {
  740            editor.toggle_single_diff_hunk(hovered_hunk, cx);
  741            cx.notify();
  742            return;
  743        } else if gutter_hitbox.is_hovered(window) {
  744            click_count = 3; // Simulate triple-click when clicking the gutter to select lines
  745        } else if !text_hitbox.is_hovered(window) {
  746            return;
  747        }
  748
  749        if EditorSettings::get_global(cx)
  750            .drag_and_drop_selection
  751            .enabled
  752            && click_count == 1
  753            && !modifiers.shift
  754        {
  755            let newest_anchor = editor.selections.newest_anchor();
  756            let snapshot = editor.snapshot(window, cx);
  757            let selection = newest_anchor.map(|anchor| anchor.to_display_point(&snapshot));
  758            if point_for_position.intersects_selection(&selection) {
  759                editor.selection_drag_state = SelectionDragState::ReadyToDrag {
  760                    selection: newest_anchor.clone(),
  761                    click_position: event.position,
  762                    mouse_down_time: Instant::now(),
  763                };
  764                cx.stop_propagation();
  765                return;
  766            }
  767        }
  768
  769        let is_singleton = editor.buffer().read(cx).is_singleton();
  770
  771        if click_count == 2 && !is_singleton {
  772            match EditorSettings::get_global(cx).double_click_in_multibuffer {
  773                DoubleClickInMultibuffer::Select => {
  774                    // do nothing special on double click, all selection logic is below
  775                }
  776                DoubleClickInMultibuffer::Open => {
  777                    if modifiers.alt {
  778                        // if double click is made with alt, pretend it's a regular double click without opening and alt,
  779                        // and run the selection logic.
  780                        modifiers.alt = false;
  781                    } else {
  782                        let scroll_position_row = position_map.scroll_position.y;
  783                        let display_row = (((event.position - gutter_hitbox.bounds.origin).y
  784                            / position_map.line_height)
  785                            as f64
  786                            + position_map.scroll_position.y)
  787                            as u32;
  788                        let multi_buffer_row = position_map
  789                            .snapshot
  790                            .display_point_to_point(
  791                                DisplayPoint::new(DisplayRow(display_row), 0),
  792                                Bias::Right,
  793                            )
  794                            .row;
  795                        let line_offset_from_top = display_row - scroll_position_row as u32;
  796                        // if double click is made without alt, open the corresponding excerp
  797                        editor.open_excerpts_common(
  798                            Some(JumpData::MultiBufferRow {
  799                                row: MultiBufferRow(multi_buffer_row),
  800                                line_offset_from_top,
  801                            }),
  802                            false,
  803                            window,
  804                            cx,
  805                        );
  806                        return;
  807                    }
  808                }
  809            }
  810        }
  811
  812        if !is_singleton {
  813            let display_row = (ScrollPixelOffset::from(
  814                (event.position - gutter_hitbox.bounds.origin).y / position_map.line_height,
  815            ) + position_map.scroll_position.y) as u32;
  816            let multi_buffer_row = position_map
  817                .snapshot
  818                .display_point_to_point(DisplayPoint::new(DisplayRow(display_row), 0), Bias::Right)
  819                .row;
  820            if line_numbers
  821                .get(&MultiBufferRow(multi_buffer_row))
  822                .is_some_and(|line_layout| {
  823                    line_layout.segments.iter().any(|segment| {
  824                        segment
  825                            .hitbox
  826                            .as_ref()
  827                            .is_some_and(|hitbox| hitbox.contains(&event.position))
  828                    })
  829                })
  830            {
  831                let line_offset_from_top = display_row - position_map.scroll_position.y as u32;
  832
  833                editor.open_excerpts_common(
  834                    Some(JumpData::MultiBufferRow {
  835                        row: MultiBufferRow(multi_buffer_row),
  836                        line_offset_from_top,
  837                    }),
  838                    modifiers.alt,
  839                    window,
  840                    cx,
  841                );
  842                cx.stop_propagation();
  843                return;
  844            }
  845        }
  846
  847        let position = point_for_position.nearest_valid;
  848        if let Some(mode) = Editor::columnar_selection_mode(&modifiers, cx) {
  849            editor.select(
  850                SelectPhase::BeginColumnar {
  851                    position,
  852                    reset: match mode {
  853                        ColumnarMode::FromMouse => true,
  854                        ColumnarMode::FromSelection => false,
  855                    },
  856                    mode,
  857                    goal_column: point_for_position.exact_unclipped.column(),
  858                },
  859                window,
  860                cx,
  861            );
  862        } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.secondary()
  863        {
  864            editor.select(
  865                SelectPhase::Extend {
  866                    position,
  867                    click_count,
  868                },
  869                window,
  870                cx,
  871            );
  872        } else {
  873            editor.select(
  874                SelectPhase::Begin {
  875                    position,
  876                    add: Editor::is_alt_pressed(&modifiers, cx),
  877                    click_count,
  878                },
  879                window,
  880                cx,
  881            );
  882        }
  883        cx.stop_propagation();
  884    }
  885
  886    fn mouse_right_down(
  887        editor: &mut Editor,
  888        event: &MouseDownEvent,
  889        position_map: &PositionMap,
  890        window: &mut Window,
  891        cx: &mut Context<Editor>,
  892    ) {
  893        if position_map.gutter_hitbox.is_hovered(window) {
  894            let gutter_right_padding = editor.gutter_dimensions.right_padding;
  895            let hitbox = &position_map.gutter_hitbox;
  896
  897            if event.position.x <= hitbox.bounds.right() - gutter_right_padding
  898                && editor.collaboration_hub.is_none()
  899            {
  900                let point_for_position = position_map.point_for_position(event.position);
  901                editor.set_gutter_context_menu(
  902                    point_for_position.nearest_valid.row(),
  903                    None,
  904                    event.position,
  905                    window,
  906                    cx,
  907                );
  908            }
  909            return;
  910        }
  911
  912        if !position_map.text_hitbox.is_hovered(window) {
  913            return;
  914        }
  915
  916        let point_for_position = position_map.point_for_position(event.position);
  917        mouse_context_menu::deploy_context_menu(
  918            editor,
  919            Some(event.position),
  920            point_for_position.nearest_valid,
  921            window,
  922            cx,
  923        );
  924        cx.stop_propagation();
  925    }
  926
  927    fn mouse_middle_down(
  928        editor: &mut Editor,
  929        event: &MouseDownEvent,
  930        position_map: &PositionMap,
  931        window: &mut Window,
  932        cx: &mut Context<Editor>,
  933    ) {
  934        if !position_map.text_hitbox.is_hovered(window) || window.default_prevented() {
  935            return;
  936        }
  937
  938        let point_for_position = position_map.point_for_position(event.position);
  939        let position = point_for_position.nearest_valid;
  940
  941        editor.select(
  942            SelectPhase::BeginColumnar {
  943                position,
  944                reset: true,
  945                mode: ColumnarMode::FromMouse,
  946                goal_column: point_for_position.exact_unclipped.column(),
  947            },
  948            window,
  949            cx,
  950        );
  951    }
  952
  953    fn mouse_up(
  954        editor: &mut Editor,
  955        event: &MouseUpEvent,
  956        position_map: &PositionMap,
  957        window: &mut Window,
  958        cx: &mut Context<Editor>,
  959    ) {
  960        // Handle diff review drag completion
  961        if editor.diff_review_drag_state.is_some() {
  962            editor.end_diff_review_drag(window, cx);
  963            cx.stop_propagation();
  964            return;
  965        }
  966
  967        let text_hitbox = &position_map.text_hitbox;
  968        let end_selection = editor.has_pending_selection();
  969        let pending_nonempty_selections = editor.has_pending_nonempty_selection();
  970        let point_for_position = position_map.point_for_position(event.position);
  971
  972        match editor.selection_drag_state {
  973            SelectionDragState::ReadyToDrag {
  974                selection: _,
  975                ref click_position,
  976                mouse_down_time: _,
  977            } => {
  978                if event.position == *click_position {
  979                    editor.select(
  980                        SelectPhase::Begin {
  981                            position: point_for_position.nearest_valid,
  982                            add: false,
  983                            click_count: 1, // ready to drag state only occurs on click count 1
  984                        },
  985                        window,
  986                        cx,
  987                    );
  988                    editor.selection_drag_state = SelectionDragState::None;
  989                    cx.stop_propagation();
  990                    return;
  991                } else {
  992                    debug_panic!("drag state can never be in ready state after drag")
  993                }
  994            }
  995            SelectionDragState::Dragging { ref selection, .. } => {
  996                let snapshot = editor.snapshot(window, cx);
  997                let selection_display = selection.map(|anchor| anchor.to_display_point(&snapshot));
  998                if !point_for_position.intersects_selection(&selection_display)
  999                    && text_hitbox.is_hovered(window)
 1000                {
 1001                    let is_cut = !(cfg!(target_os = "macos") && event.modifiers.alt
 1002                        || cfg!(not(target_os = "macos")) && event.modifiers.control);
 1003                    editor.move_selection_on_drop(
 1004                        &selection.clone(),
 1005                        point_for_position.nearest_valid,
 1006                        is_cut,
 1007                        window,
 1008                        cx,
 1009                    );
 1010                }
 1011                editor.selection_drag_state = SelectionDragState::None;
 1012                cx.stop_propagation();
 1013                cx.notify();
 1014                return;
 1015            }
 1016            _ => {}
 1017        }
 1018
 1019        if end_selection {
 1020            editor.select(SelectPhase::End, window, cx);
 1021        }
 1022
 1023        if end_selection && pending_nonempty_selections {
 1024            cx.stop_propagation();
 1025        } else if cfg!(any(target_os = "linux", target_os = "freebsd"))
 1026            && event.button == MouseButton::Middle
 1027        {
 1028            #[allow(
 1029                clippy::collapsible_if,
 1030                clippy::needless_return,
 1031                reason = "The cfg-block below makes this a false positive"
 1032            )]
 1033            if !text_hitbox.is_hovered(window) || editor.read_only(cx) {
 1034                return;
 1035            }
 1036
 1037            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 1038            if EditorSettings::get_global(cx).middle_click_paste {
 1039                if let Some(text) = cx.read_from_primary().and_then(|item| item.text()) {
 1040                    let point_for_position = position_map.point_for_position(event.position);
 1041                    let position = point_for_position.nearest_valid;
 1042
 1043                    editor.select(
 1044                        SelectPhase::Begin {
 1045                            position,
 1046                            add: false,
 1047                            click_count: 1,
 1048                        },
 1049                        window,
 1050                        cx,
 1051                    );
 1052                    editor.insert(&text, window, cx);
 1053                }
 1054                cx.stop_propagation()
 1055            }
 1056        }
 1057    }
 1058
 1059    fn click(
 1060        editor: &mut Editor,
 1061        event: &ClickEvent,
 1062        position_map: &PositionMap,
 1063        window: &mut Window,
 1064        cx: &mut Context<Editor>,
 1065    ) {
 1066        let text_hitbox = &position_map.text_hitbox;
 1067        let pending_nonempty_selections = editor.has_pending_nonempty_selection();
 1068
 1069        let hovered_link_modifier = Editor::is_cmd_or_ctrl_pressed(&event.modifiers(), cx);
 1070        let mouse_down_hovered_link_modifier = if let ClickEvent::Mouse(mouse_event) = event {
 1071            Editor::is_cmd_or_ctrl_pressed(&mouse_event.down.modifiers, cx)
 1072        } else {
 1073            true
 1074        };
 1075
 1076        if let Some(mouse_position) = event.mouse_position()
 1077            && !pending_nonempty_selections
 1078            && hovered_link_modifier
 1079            && mouse_down_hovered_link_modifier
 1080            && text_hitbox.is_hovered(window)
 1081            && !matches!(
 1082                editor.selection_drag_state,
 1083                SelectionDragState::Dragging { .. }
 1084            )
 1085        {
 1086            let point = position_map.point_for_position(mouse_position);
 1087            editor.handle_click_hovered_link(point, event.modifiers(), window, cx);
 1088            editor.selection_drag_state = SelectionDragState::None;
 1089
 1090            cx.stop_propagation();
 1091        }
 1092    }
 1093
 1094    fn pressure_click(
 1095        editor: &mut Editor,
 1096        event: &MousePressureEvent,
 1097        position_map: &PositionMap,
 1098        window: &mut Window,
 1099        cx: &mut Context<Editor>,
 1100    ) {
 1101        let text_hitbox = &position_map.text_hitbox;
 1102        let force_click_possible =
 1103            matches!(editor.prev_pressure_stage, Some(PressureStage::Normal))
 1104                && event.stage == PressureStage::Force;
 1105
 1106        editor.prev_pressure_stage = Some(event.stage);
 1107
 1108        if force_click_possible && text_hitbox.is_hovered(window) {
 1109            let point = position_map.point_for_position(event.position);
 1110            editor.handle_click_hovered_link(point, event.modifiers, window, cx);
 1111            editor.selection_drag_state = SelectionDragState::None;
 1112            cx.stop_propagation();
 1113        }
 1114    }
 1115
 1116    fn mouse_dragged(
 1117        editor: &mut Editor,
 1118        event: &MouseMoveEvent,
 1119        position_map: &PositionMap,
 1120        window: &mut Window,
 1121        cx: &mut Context<Editor>,
 1122    ) {
 1123        if !editor.has_pending_selection()
 1124            && matches!(editor.selection_drag_state, SelectionDragState::None)
 1125        {
 1126            return;
 1127        }
 1128
 1129        let point_for_position = position_map.point_for_position(event.position);
 1130        let text_hitbox = &position_map.text_hitbox;
 1131
 1132        let scroll_delta = {
 1133            let text_bounds = text_hitbox.bounds;
 1134            let mut scroll_delta = gpui::Point::<f32>::default();
 1135            let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
 1136            let top = text_bounds.origin.y + vertical_margin;
 1137            let bottom = text_bounds.bottom_left().y - vertical_margin;
 1138            if event.position.y < top {
 1139                scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y);
 1140            }
 1141            if event.position.y > bottom {
 1142                scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
 1143            }
 1144
 1145            // We need horizontal width of text
 1146            let style = editor.style.clone().unwrap_or_default();
 1147            let font_id = window.text_system().resolve_font(&style.text.font());
 1148            let font_size = style.text.font_size.to_pixels(window.rem_size());
 1149            let em_width = window.text_system().em_width(font_id, font_size).unwrap();
 1150
 1151            let scroll_margin_x = EditorSettings::get_global(cx).horizontal_scroll_margin;
 1152
 1153            let scroll_space: Pixels = scroll_margin_x * em_width;
 1154
 1155            let left = text_bounds.origin.x + scroll_space;
 1156            let right = text_bounds.top_right().x - scroll_space;
 1157
 1158            if event.position.x < left {
 1159                scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
 1160            }
 1161            if event.position.x > right {
 1162                scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
 1163            }
 1164            scroll_delta
 1165        };
 1166
 1167        if !editor.has_pending_selection() {
 1168            let drop_anchor = position_map
 1169                .snapshot
 1170                .display_point_to_anchor(point_for_position.nearest_valid, Bias::Left);
 1171            match editor.selection_drag_state {
 1172                SelectionDragState::Dragging {
 1173                    ref mut drop_cursor,
 1174                    ref mut hide_drop_cursor,
 1175                    ..
 1176                } => {
 1177                    drop_cursor.start = drop_anchor;
 1178                    drop_cursor.end = drop_anchor;
 1179                    *hide_drop_cursor = !text_hitbox.is_hovered(window);
 1180                    editor.apply_scroll_delta(scroll_delta, window, cx);
 1181                    cx.notify();
 1182                }
 1183                SelectionDragState::ReadyToDrag {
 1184                    ref selection,
 1185                    ref click_position,
 1186                    ref mouse_down_time,
 1187                } => {
 1188                    let drag_and_drop_delay = Duration::from_millis(
 1189                        EditorSettings::get_global(cx)
 1190                            .drag_and_drop_selection
 1191                            .delay
 1192                            .0,
 1193                    );
 1194                    if mouse_down_time.elapsed() >= drag_and_drop_delay {
 1195                        let drop_cursor = Selection {
 1196                            id: post_inc(&mut editor.selections.next_selection_id()),
 1197                            start: drop_anchor,
 1198                            end: drop_anchor,
 1199                            reversed: false,
 1200                            goal: SelectionGoal::None,
 1201                        };
 1202                        editor.selection_drag_state = SelectionDragState::Dragging {
 1203                            selection: selection.clone(),
 1204                            drop_cursor,
 1205                            hide_drop_cursor: false,
 1206                        };
 1207                        editor.apply_scroll_delta(scroll_delta, window, cx);
 1208                        cx.notify();
 1209                    } else {
 1210                        let click_point = position_map.point_for_position(*click_position);
 1211                        editor.selection_drag_state = SelectionDragState::None;
 1212                        editor.select(
 1213                            SelectPhase::Begin {
 1214                                position: click_point.nearest_valid,
 1215                                add: false,
 1216                                click_count: 1,
 1217                            },
 1218                            window,
 1219                            cx,
 1220                        );
 1221                        editor.select(
 1222                            SelectPhase::Update {
 1223                                position: point_for_position.nearest_valid,
 1224                                goal_column: point_for_position.exact_unclipped.column(),
 1225                                scroll_delta,
 1226                            },
 1227                            window,
 1228                            cx,
 1229                        );
 1230                    }
 1231                }
 1232                _ => {}
 1233            }
 1234        } else {
 1235            editor.select(
 1236                SelectPhase::Update {
 1237                    position: point_for_position.nearest_valid,
 1238                    goal_column: point_for_position.exact_unclipped.column(),
 1239                    scroll_delta,
 1240                },
 1241                window,
 1242                cx,
 1243            );
 1244        }
 1245    }
 1246
 1247    pub(crate) fn mouse_moved(
 1248        editor: &mut Editor,
 1249        event: &MouseMoveEvent,
 1250        position_map: &PositionMap,
 1251        split_side: Option<SplitSide>,
 1252        window: &mut Window,
 1253        cx: &mut Context<Editor>,
 1254    ) {
 1255        let text_hitbox = &position_map.text_hitbox;
 1256        let gutter_hitbox = &position_map.gutter_hitbox;
 1257        let modifiers = event.modifiers;
 1258        let text_hovered = text_hitbox.is_hovered(window);
 1259        let gutter_hovered = gutter_hitbox.is_hovered(window);
 1260        editor.set_gutter_hovered(gutter_hovered, cx);
 1261        editor.show_mouse_cursor(cx);
 1262
 1263        let point_for_position = position_map.point_for_position(event.position);
 1264        let valid_point = point_for_position.nearest_valid;
 1265
 1266        // Update diff review drag state if we're dragging
 1267        if editor.diff_review_drag_state.is_some() {
 1268            editor.update_diff_review_drag(valid_point.row(), window, cx);
 1269        }
 1270
 1271        let hovered_diff_control = position_map
 1272            .diff_hunk_control_bounds
 1273            .iter()
 1274            .find(|(_, bounds)| bounds.contains(&event.position))
 1275            .map(|(row, _)| *row);
 1276
 1277        let hovered_diff_hunk_row = if let Some(control_row) = hovered_diff_control {
 1278            Some(control_row)
 1279        } else if text_hovered {
 1280            let current_row = valid_point.row();
 1281            position_map.display_hunks.iter().find_map(|(hunk, _)| {
 1282                if let DisplayDiffHunk::Unfolded {
 1283                    display_row_range, ..
 1284                } = hunk
 1285                {
 1286                    if display_row_range.contains(&current_row) {
 1287                        Some(display_row_range.start)
 1288                    } else {
 1289                        None
 1290                    }
 1291                } else {
 1292                    None
 1293                }
 1294            })
 1295        } else {
 1296            None
 1297        };
 1298
 1299        if hovered_diff_hunk_row != editor.hovered_diff_hunk_row {
 1300            editor.hovered_diff_hunk_row = hovered_diff_hunk_row;
 1301            cx.notify();
 1302        }
 1303
 1304        if text_hovered
 1305            && let Some((bounds, buffer_id, blame_entry)) = &position_map.inline_blame_bounds
 1306        {
 1307            let mouse_over_inline_blame = bounds.contains(&event.position);
 1308            let mouse_over_popover = editor
 1309                .inline_blame_popover
 1310                .as_ref()
 1311                .and_then(|state| state.popover_bounds)
 1312                .is_some_and(|bounds| bounds.contains(&event.position));
 1313            let keyboard_grace = editor
 1314                .inline_blame_popover
 1315                .as_ref()
 1316                .is_some_and(|state| state.keyboard_grace);
 1317
 1318            if mouse_over_inline_blame || mouse_over_popover {
 1319                editor.show_blame_popover(*buffer_id, blame_entry, event.position, false, cx);
 1320            } else if !keyboard_grace {
 1321                editor.hide_blame_popover(false, cx);
 1322            }
 1323        } else {
 1324            let keyboard_grace = editor
 1325                .inline_blame_popover
 1326                .as_ref()
 1327                .is_some_and(|state| state.keyboard_grace);
 1328            if !keyboard_grace {
 1329                editor.hide_blame_popover(false, cx);
 1330            }
 1331        }
 1332
 1333        // Handle diff review indicator when gutter is hovered in diff mode with AI enabled
 1334        let show_diff_review = editor.show_diff_review_button()
 1335            && cx.has_flag::<DiffReviewFeatureFlag>()
 1336            && !DisableAiSettings::is_ai_disabled_for_buffer(
 1337                editor.buffer.read(cx).as_singleton().as_ref(),
 1338                cx,
 1339            );
 1340
 1341        let diff_review_indicator = if gutter_hovered && show_diff_review {
 1342            let is_visible = editor
 1343                .gutter_diff_review_indicator
 1344                .0
 1345                .is_some_and(|indicator| indicator.is_active);
 1346
 1347            if !is_visible {
 1348                editor
 1349                    .gutter_diff_review_indicator
 1350                    .1
 1351                    .get_or_insert_with(|| {
 1352                        cx.spawn(async move |this, cx| {
 1353                            cx.background_executor()
 1354                                .timer(Duration::from_millis(200))
 1355                                .await;
 1356
 1357                            this.update(cx, |this, cx| {
 1358                                if let Some(indicator) =
 1359                                    this.gutter_diff_review_indicator.0.as_mut()
 1360                                {
 1361                                    indicator.is_active = true;
 1362                                    cx.notify();
 1363                                }
 1364                            })
 1365                            .ok();
 1366                        })
 1367                    });
 1368            }
 1369
 1370            let anchor = position_map
 1371                .snapshot
 1372                .display_point_to_anchor(valid_point, Bias::Left);
 1373            Some(PhantomDiffReviewIndicator {
 1374                start: anchor,
 1375                end: anchor,
 1376                is_active: is_visible,
 1377            })
 1378        } else {
 1379            editor.gutter_diff_review_indicator.1 = None;
 1380            None
 1381        };
 1382
 1383        if diff_review_indicator != editor.gutter_diff_review_indicator.0 {
 1384            editor.gutter_diff_review_indicator.0 = diff_review_indicator;
 1385            cx.notify();
 1386        }
 1387
 1388        // Don't show breakpoint indicator when diff review indicator is active on this row
 1389        let is_on_diff_review_button_row = diff_review_indicator.is_some_and(|indicator| {
 1390            let start_row = indicator
 1391                .start
 1392                .to_display_point(&position_map.snapshot.display_snapshot)
 1393                .row();
 1394            indicator.is_active && start_row == valid_point.row()
 1395        });
 1396
 1397        let breakpoint_indicator = if gutter_hovered
 1398            && !is_on_diff_review_button_row
 1399            && split_side != Some(SplitSide::Left)
 1400        {
 1401            let buffer_anchor = position_map
 1402                .snapshot
 1403                .display_point_to_anchor(valid_point, Bias::Left);
 1404
 1405            if position_map
 1406                .snapshot
 1407                .buffer_snapshot()
 1408                .anchor_to_buffer_anchor(buffer_anchor)
 1409                .is_some()
 1410            {
 1411                let is_visible = editor
 1412                    .gutter_hover_button
 1413                    .0
 1414                    .is_some_and(|indicator| indicator.is_active);
 1415
 1416                if !is_visible {
 1417                    editor.gutter_hover_button.1.get_or_insert_with(|| {
 1418                        cx.spawn(async move |this, cx| {
 1419                            cx.background_executor()
 1420                                .timer(Duration::from_millis(200))
 1421                                .await;
 1422
 1423                            this.update(cx, |this, cx| {
 1424                                if let Some(indicator) = this.gutter_hover_button.0.as_mut() {
 1425                                    indicator.is_active = true;
 1426                                    cx.notify();
 1427                                }
 1428                            })
 1429                            .ok();
 1430                        })
 1431                    });
 1432                }
 1433
 1434                Some(GutterHoverButton {
 1435                    display_row: valid_point.row(),
 1436                    is_active: is_visible,
 1437                })
 1438            } else {
 1439                editor.gutter_hover_button.1 = None;
 1440                None
 1441            }
 1442        } else {
 1443            editor.gutter_hover_button.1 = None;
 1444            None
 1445        };
 1446
 1447        if &breakpoint_indicator != &editor.gutter_hover_button.0 {
 1448            editor.gutter_hover_button.0 = breakpoint_indicator;
 1449            cx.notify();
 1450        }
 1451
 1452        // Don't trigger hover popover if mouse is hovering over context menu
 1453        if text_hovered {
 1454            editor.update_hovered_link(
 1455                point_for_position,
 1456                Some(event.position),
 1457                &position_map.snapshot,
 1458                modifiers,
 1459                window,
 1460                cx,
 1461            );
 1462
 1463            if let Some(point) = point_for_position.as_valid() {
 1464                let anchor = position_map
 1465                    .snapshot
 1466                    .buffer_snapshot()
 1467                    .anchor_before(point.to_offset(&position_map.snapshot, Bias::Left));
 1468                hover_at(editor, Some(anchor), Some(event.position), window, cx);
 1469                Self::update_visible_cursor(editor, point, position_map, window, cx);
 1470            } else {
 1471                editor.update_inlay_link_and_hover_points(
 1472                    &position_map.snapshot,
 1473                    point_for_position,
 1474                    Some(event.position),
 1475                    modifiers.secondary(),
 1476                    modifiers.shift,
 1477                    window,
 1478                    cx,
 1479                );
 1480            }
 1481        } else {
 1482            editor.hide_hovered_link(cx);
 1483            hover_at(editor, None, Some(event.position), window, cx);
 1484        }
 1485    }
 1486
 1487    fn update_visible_cursor(
 1488        editor: &mut Editor,
 1489        point: DisplayPoint,
 1490        position_map: &PositionMap,
 1491        window: &mut Window,
 1492        cx: &mut Context<Editor>,
 1493    ) {
 1494        let snapshot = &position_map.snapshot;
 1495        let Some(hub) = editor.collaboration_hub() else {
 1496            return;
 1497        };
 1498        let start = snapshot.display_snapshot.clip_point(
 1499            DisplayPoint::new(point.row(), point.column().saturating_sub(1)),
 1500            Bias::Left,
 1501        );
 1502        let end = snapshot.display_snapshot.clip_point(
 1503            DisplayPoint::new(
 1504                point.row(),
 1505                (point.column() + 1).min(snapshot.line_len(point.row())),
 1506            ),
 1507            Bias::Right,
 1508        );
 1509
 1510        let range = snapshot
 1511            .buffer_snapshot()
 1512            .anchor_before(start.to_point(&snapshot.display_snapshot))
 1513            ..snapshot
 1514                .buffer_snapshot()
 1515                .anchor_after(end.to_point(&snapshot.display_snapshot));
 1516
 1517        let Some(selection) = snapshot.remote_selections_in_range(&range, hub, cx).next() else {
 1518            return;
 1519        };
 1520        let key = crate::HoveredCursor {
 1521            replica_id: selection.replica_id,
 1522            selection_id: selection.selection.id,
 1523        };
 1524        editor.hovered_cursors.insert(
 1525            key.clone(),
 1526            cx.spawn_in(window, async move |editor, cx| {
 1527                cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
 1528                editor
 1529                    .update(cx, |editor, cx| {
 1530                        editor.hovered_cursors.remove(&key);
 1531                        cx.notify();
 1532                    })
 1533                    .ok();
 1534            }),
 1535        );
 1536        cx.notify()
 1537    }
 1538
 1539    fn layout_selections(
 1540        &self,
 1541        start_anchor: Anchor,
 1542        end_anchor: Anchor,
 1543        local_selections: &[Selection<Point>],
 1544        snapshot: &EditorSnapshot,
 1545        start_row: DisplayRow,
 1546        end_row: DisplayRow,
 1547        window: &mut Window,
 1548        cx: &mut App,
 1549    ) -> (
 1550        Vec<(PlayerColor, Vec<SelectionLayout>)>,
 1551        BTreeMap<DisplayRow, LineHighlightSpec>,
 1552        Option<DisplayPoint>,
 1553    ) {
 1554        let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
 1555        let mut active_rows = BTreeMap::new();
 1556        let mut newest_selection_head = None;
 1557
 1558        let Some(editor_with_selections) = self.editor_with_selections(cx) else {
 1559            return (selections, active_rows, newest_selection_head);
 1560        };
 1561
 1562        editor_with_selections.update(cx, |editor, cx| {
 1563            if editor.show_local_selections {
 1564                let mut layouts = Vec::new();
 1565                let newest = editor.selections.newest(&editor.display_snapshot(cx));
 1566                for selection in local_selections.iter().cloned() {
 1567                    let is_empty = selection.start == selection.end;
 1568                    let is_newest = selection == newest;
 1569
 1570                    let layout = SelectionLayout::new(
 1571                        selection,
 1572                        editor.selections.line_mode(),
 1573                        editor.cursor_offset_on_selection,
 1574                        editor.cursor_shape,
 1575                        &snapshot.display_snapshot,
 1576                        is_newest,
 1577                        editor.leader_id.is_none(),
 1578                        None,
 1579                    );
 1580                    if is_newest {
 1581                        newest_selection_head = Some(layout.head);
 1582                    }
 1583
 1584                    for row in cmp::max(layout.active_rows.start.0, start_row.0)
 1585                        ..=cmp::min(layout.active_rows.end.0, end_row.0)
 1586                    {
 1587                        let contains_non_empty_selection = active_rows
 1588                            .entry(DisplayRow(row))
 1589                            .or_insert_with(LineHighlightSpec::default);
 1590                        contains_non_empty_selection.selection |= !is_empty;
 1591                    }
 1592                    layouts.push(layout);
 1593                }
 1594
 1595                let mut player = editor.current_user_player_color(cx);
 1596                if !editor.is_focused(window) {
 1597                    const UNFOCUS_EDITOR_SELECTION_OPACITY: f32 = 0.5;
 1598                    player.selection = player.selection.opacity(UNFOCUS_EDITOR_SELECTION_OPACITY);
 1599                }
 1600                selections.push((player, layouts));
 1601
 1602                if let SelectionDragState::Dragging {
 1603                    ref selection,
 1604                    ref drop_cursor,
 1605                    ref hide_drop_cursor,
 1606                } = editor.selection_drag_state
 1607                    && !hide_drop_cursor
 1608                    && (drop_cursor
 1609                        .start
 1610                        .cmp(&selection.start, &snapshot.buffer_snapshot())
 1611                        .eq(&Ordering::Less)
 1612                        || drop_cursor
 1613                            .end
 1614                            .cmp(&selection.end, &snapshot.buffer_snapshot())
 1615                            .eq(&Ordering::Greater))
 1616                {
 1617                    let drag_cursor_layout = SelectionLayout::new(
 1618                        drop_cursor.clone(),
 1619                        false,
 1620                        editor.cursor_offset_on_selection,
 1621                        CursorShape::Bar,
 1622                        &snapshot.display_snapshot,
 1623                        false,
 1624                        false,
 1625                        None,
 1626                    );
 1627                    let absent_color = cx.theme().players().absent();
 1628                    selections.push((absent_color, vec![drag_cursor_layout]));
 1629                }
 1630            }
 1631
 1632            if let Some(collaboration_hub) = &editor.collaboration_hub {
 1633                // When following someone, render the local selections in their color.
 1634                if let Some(leader_id) = editor.leader_id {
 1635                    match leader_id {
 1636                        CollaboratorId::PeerId(peer_id) => {
 1637                            if let Some(collaborator) =
 1638                                collaboration_hub.collaborators(cx).get(&peer_id)
 1639                                && let Some(participant_index) = collaboration_hub
 1640                                    .user_participant_indices(cx)
 1641                                    .get(&collaborator.user_id)
 1642                                && let Some((local_selection_style, _)) = selections.first_mut()
 1643                            {
 1644                                *local_selection_style = cx
 1645                                    .theme()
 1646                                    .players()
 1647                                    .color_for_participant(participant_index.0);
 1648                            }
 1649                        }
 1650                        CollaboratorId::Agent => {
 1651                            if let Some((local_selection_style, _)) = selections.first_mut() {
 1652                                *local_selection_style = cx.theme().players().agent();
 1653                            }
 1654                        }
 1655                    }
 1656                }
 1657
 1658                let mut remote_selections = HashMap::default();
 1659                for selection in snapshot.remote_selections_in_range(
 1660                    &(start_anchor..end_anchor),
 1661                    collaboration_hub.as_ref(),
 1662                    cx,
 1663                ) {
 1664                    // Don't re-render the leader's selections, since the local selections
 1665                    // match theirs.
 1666                    if Some(selection.collaborator_id) == editor.leader_id {
 1667                        continue;
 1668                    }
 1669                    let key = HoveredCursor {
 1670                        replica_id: selection.replica_id,
 1671                        selection_id: selection.selection.id,
 1672                    };
 1673
 1674                    let is_shown =
 1675                        editor.show_cursor_names || editor.hovered_cursors.contains_key(&key);
 1676
 1677                    remote_selections
 1678                        .entry(selection.replica_id)
 1679                        .or_insert((selection.color, Vec::new()))
 1680                        .1
 1681                        .push(SelectionLayout::new(
 1682                            selection.selection,
 1683                            selection.line_mode,
 1684                            editor.cursor_offset_on_selection,
 1685                            selection.cursor_shape,
 1686                            &snapshot.display_snapshot,
 1687                            false,
 1688                            false,
 1689                            if is_shown { selection.user_name } else { None },
 1690                        ));
 1691                }
 1692
 1693                selections.extend(remote_selections.into_values());
 1694            } else if !editor.is_focused(window) && editor.show_cursor_when_unfocused {
 1695                let cursor_offset_on_selection = editor.cursor_offset_on_selection;
 1696
 1697                let layouts = snapshot
 1698                    .buffer_snapshot()
 1699                    .selections_in_range(&(start_anchor..end_anchor), true)
 1700                    .map(move |(_, line_mode, cursor_shape, selection)| {
 1701                        SelectionLayout::new(
 1702                            selection,
 1703                            line_mode,
 1704                            cursor_offset_on_selection,
 1705                            cursor_shape,
 1706                            &snapshot.display_snapshot,
 1707                            false,
 1708                            false,
 1709                            None,
 1710                        )
 1711                    })
 1712                    .collect::<Vec<_>>();
 1713                let player = editor.current_user_player_color(cx);
 1714                selections.push((player, layouts));
 1715            }
 1716        });
 1717
 1718        #[cfg(debug_assertions)]
 1719        Self::layout_debug_ranges(
 1720            &mut selections,
 1721            start_anchor..end_anchor,
 1722            &snapshot.display_snapshot,
 1723            cx,
 1724        );
 1725
 1726        (selections, active_rows, newest_selection_head)
 1727    }
 1728
 1729    fn collect_cursors(
 1730        &self,
 1731        snapshot: &EditorSnapshot,
 1732        cx: &mut App,
 1733    ) -> Vec<(DisplayPoint, Hsla)> {
 1734        let editor = self.editor.read(cx);
 1735        let mut cursors = Vec::new();
 1736        let mut skip_local = false;
 1737        let mut add_cursor = |anchor: Anchor, color| {
 1738            cursors.push((anchor.to_display_point(&snapshot.display_snapshot), color));
 1739        };
 1740        // Remote cursors
 1741        if let Some(collaboration_hub) = &editor.collaboration_hub {
 1742            for remote_selection in snapshot.remote_selections_in_range(
 1743                &(Anchor::Min..Anchor::Max),
 1744                collaboration_hub.deref(),
 1745                cx,
 1746            ) {
 1747                add_cursor(
 1748                    remote_selection.selection.head(),
 1749                    remote_selection.color.cursor,
 1750                );
 1751                if Some(remote_selection.collaborator_id) == editor.leader_id {
 1752                    skip_local = true;
 1753                }
 1754            }
 1755        }
 1756        // Local cursors
 1757        if !skip_local {
 1758            let color = cx.theme().players().local().cursor;
 1759            editor
 1760                .selections
 1761                .disjoint_anchors()
 1762                .iter()
 1763                .for_each(|selection| {
 1764                    add_cursor(selection.head(), color);
 1765                });
 1766            if let Some(ref selection) = editor.selections.pending_anchor() {
 1767                add_cursor(selection.head(), color);
 1768            }
 1769        }
 1770        cursors
 1771    }
 1772
 1773    fn layout_visible_cursors(
 1774        &self,
 1775        snapshot: &EditorSnapshot,
 1776        selections: &[(PlayerColor, Vec<SelectionLayout>)],
 1777        row_block_types: &HashMap<DisplayRow, bool>,
 1778        visible_display_row_range: Range<DisplayRow>,
 1779        line_layouts: &[LineWithInvisibles],
 1780        text_hitbox: &Hitbox,
 1781        content_origin: gpui::Point<Pixels>,
 1782        scroll_position: gpui::Point<ScrollOffset>,
 1783        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 1784        line_height: Pixels,
 1785        em_width: Pixels,
 1786        em_advance: Pixels,
 1787        autoscroll_containing_element: bool,
 1788        redacted_ranges: &[Range<DisplayPoint>],
 1789        window: &mut Window,
 1790        cx: &mut App,
 1791    ) -> Vec<CursorLayout> {
 1792        let mut autoscroll_bounds = None;
 1793        let cursor_layouts = self.editor.update(cx, |editor, cx| {
 1794            let mut cursors = Vec::new();
 1795
 1796            let show_local_cursors = editor.show_local_cursors(window, cx);
 1797
 1798            for (player_color, selections) in selections {
 1799                for selection in selections {
 1800                    let cursor_position = selection.head;
 1801
 1802                    let in_range = visible_display_row_range.contains(&cursor_position.row());
 1803                    if (selection.is_local && !show_local_cursors)
 1804                        || !in_range
 1805                        || row_block_types.get(&cursor_position.row()) == Some(&true)
 1806                    {
 1807                        continue;
 1808                    }
 1809
 1810                    let cursor_row_layout = &line_layouts
 1811                        [cursor_position.row().minus(visible_display_row_range.start) as usize];
 1812                    let cursor_column = cursor_position.column() as usize;
 1813
 1814                    let cursor_character_x = cursor_row_layout.x_for_index(cursor_column)
 1815                        + cursor_row_layout
 1816                            .alignment_offset(self.style.text.text_align, text_hitbox.size.width);
 1817                    let cursor_next_x = cursor_row_layout.x_for_index(cursor_column + 1)
 1818                        + cursor_row_layout
 1819                            .alignment_offset(self.style.text.text_align, text_hitbox.size.width);
 1820                    let mut cell_width = cursor_next_x - cursor_character_x;
 1821                    if cell_width == Pixels::ZERO {
 1822                        cell_width = em_advance;
 1823                    }
 1824
 1825                    let mut block_width = cell_width;
 1826                    let mut block_text = None;
 1827
 1828                    let is_cursor_in_redacted_range = redacted_ranges
 1829                        .iter()
 1830                        .any(|range| range.start <= cursor_position && cursor_position < range.end);
 1831
 1832                    if selection.cursor_shape == CursorShape::Block && !is_cursor_in_redacted_range
 1833                    {
 1834                        if let Some(text) = snapshot.grapheme_at(cursor_position).or_else(|| {
 1835                            if snapshot.is_empty() {
 1836                                snapshot.placeholder_text().and_then(|s| {
 1837                                    s.graphemes(true).next().map(|s| s.to_string().into())
 1838                                })
 1839                            } else {
 1840                                None
 1841                            }
 1842                        }) {
 1843                            let is_ascii_whitespace_only =
 1844                                text.as_ref().chars().all(|c| c.is_ascii_whitespace());
 1845                            let len = text.len();
 1846
 1847                            let mut font = cursor_row_layout
 1848                                .font_id_for_index(cursor_column)
 1849                                .and_then(|cursor_font_id| {
 1850                                    window.text_system().get_font_for_id(cursor_font_id)
 1851                                })
 1852                                .unwrap_or(self.style.text.font());
 1853                            font.features = self.style.text.font_features.clone();
 1854
 1855                            // Invert the text color for the block cursor. Ensure that the text
 1856                            // color is opaque enough to be visible against the background color.
 1857                            //
 1858                            // 0.75 is an arbitrary threshold to determine if the background color is
 1859                            // opaque enough to use as a text color.
 1860                            //
 1861                            // TODO: In the future we should ensure themes have a `text_inverse` color.
 1862                            let color = if cx.theme().colors().editor_background.a < 0.75 {
 1863                                match cx.theme().appearance {
 1864                                    Appearance::Dark => Hsla::black(),
 1865                                    Appearance::Light => Hsla::white(),
 1866                                }
 1867                            } else {
 1868                                cx.theme().colors().editor_background
 1869                            };
 1870
 1871                            let shaped = window.text_system().shape_line(
 1872                                text,
 1873                                cursor_row_layout.font_size,
 1874                                &[TextRun {
 1875                                    len,
 1876                                    font,
 1877                                    color,
 1878                                    ..Default::default()
 1879                                }],
 1880                                None,
 1881                            );
 1882                            if !is_ascii_whitespace_only {
 1883                                block_width = block_width.max(shaped.width);
 1884                            }
 1885                            block_text = Some(shaped);
 1886                        }
 1887                    }
 1888
 1889                    let x = cursor_character_x - scroll_pixel_position.x.into();
 1890                    let y = ((cursor_position.row().as_f64() - scroll_position.y)
 1891                        * ScrollPixelOffset::from(line_height))
 1892                    .into();
 1893                    if selection.is_newest {
 1894                        editor.pixel_position_of_newest_cursor = Some(point(
 1895                            text_hitbox.origin.x + x + block_width / 2.,
 1896                            text_hitbox.origin.y + y + line_height / 2.,
 1897                        ));
 1898
 1899                        if autoscroll_containing_element {
 1900                            let top = text_hitbox.origin.y
 1901                                + ((cursor_position.row().as_f64() - scroll_position.y - 3.)
 1902                                    .max(0.)
 1903                                    * ScrollPixelOffset::from(line_height))
 1904                                .into();
 1905                            let left = text_hitbox.origin.x
 1906                                + ((cursor_position.column() as ScrollOffset
 1907                                    - scroll_position.x
 1908                                    - 3.)
 1909                                    .max(0.)
 1910                                    * ScrollPixelOffset::from(em_width))
 1911                                .into();
 1912
 1913                            let bottom = text_hitbox.origin.y
 1914                                + ((cursor_position.row().as_f64() - scroll_position.y + 4.)
 1915                                    * ScrollPixelOffset::from(line_height))
 1916                                .into();
 1917                            let right = text_hitbox.origin.x
 1918                                + ((cursor_position.column() as ScrollOffset - scroll_position.x
 1919                                    + 4.)
 1920                                    * ScrollPixelOffset::from(em_width))
 1921                                .into();
 1922
 1923                            autoscroll_bounds =
 1924                                Some(Bounds::from_corners(point(left, top), point(right, bottom)))
 1925                        }
 1926                    }
 1927
 1928                    let mut cursor = CursorLayout {
 1929                        color: player_color.cursor,
 1930                        block_width,
 1931                        origin: point(x, y),
 1932                        line_height,
 1933                        shape: selection.cursor_shape,
 1934                        block_text,
 1935                        cursor_name: None,
 1936                    };
 1937                    let cursor_name = selection.user_name.clone().map(|name| CursorName {
 1938                        string: name,
 1939                        color: self.style.background,
 1940                        is_top_row: cursor_position.row().0 == 0,
 1941                    });
 1942                    cursor.layout(content_origin, cursor_name, window, cx);
 1943                    cursors.push(cursor);
 1944                }
 1945            }
 1946
 1947            cursors
 1948        });
 1949
 1950        if let Some(bounds) = autoscroll_bounds {
 1951            window.request_autoscroll(bounds);
 1952        }
 1953
 1954        cursor_layouts
 1955    }
 1956
 1957    fn layout_scrollbars(
 1958        &self,
 1959        snapshot: &EditorSnapshot,
 1960        scrollbar_layout_information: &ScrollbarLayoutInformation,
 1961        content_offset: gpui::Point<Pixels>,
 1962        scroll_position: gpui::Point<ScrollOffset>,
 1963        non_visible_cursors: bool,
 1964        right_margin: Pixels,
 1965        editor_width: Pixels,
 1966        window: &mut Window,
 1967        cx: &mut App,
 1968    ) -> Option<EditorScrollbars> {
 1969        let show_scrollbars = self.editor.read(cx).show_scrollbars;
 1970        if (!show_scrollbars.horizontal && !show_scrollbars.vertical)
 1971            || self.style.scrollbar_width.is_zero()
 1972        {
 1973            return None;
 1974        }
 1975
 1976        // If a drag took place after we started dragging the scrollbar,
 1977        // cancel the scrollbar drag.
 1978        if cx.has_active_drag() {
 1979            self.editor.update(cx, |editor, cx| {
 1980                editor.scroll_manager.reset_scrollbar_state(cx)
 1981            });
 1982        }
 1983
 1984        let editor_settings = EditorSettings::get_global(cx);
 1985        let scrollbar_settings = editor_settings.scrollbar;
 1986        let show_scrollbars = match scrollbar_settings.show {
 1987            ShowScrollbar::Auto => {
 1988                let editor = self.editor.read(cx);
 1989                let is_singleton = editor.buffer_kind(cx) == ItemBufferKind::Singleton;
 1990                // Git
 1991                (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot().has_diff_hunks())
 1992                ||
 1993                // Buffer Search Results
 1994                (is_singleton && scrollbar_settings.search_results && editor.has_background_highlights(HighlightKey::BufferSearchHighlights))
 1995                ||
 1996                // Selected Text Occurrences
 1997                (is_singleton && scrollbar_settings.selected_text && editor.has_background_highlights(HighlightKey::SelectedTextHighlight))
 1998                ||
 1999                // Selected Symbol Occurrences
 2000                (is_singleton && scrollbar_settings.selected_symbol && (editor.has_background_highlights(HighlightKey::DocumentHighlightRead) || editor.has_background_highlights(HighlightKey::DocumentHighlightWrite)))
 2001                ||
 2002                // Diagnostics
 2003                (is_singleton && scrollbar_settings.diagnostics != ScrollbarDiagnostics::None && snapshot.buffer_snapshot().has_diagnostics())
 2004                ||
 2005                // Cursors out of sight
 2006                non_visible_cursors
 2007                ||
 2008                // Scrollmanager
 2009                editor.scroll_manager.scrollbars_visible()
 2010            }
 2011            ShowScrollbar::System => self.editor.read(cx).scroll_manager.scrollbars_visible(),
 2012            ShowScrollbar::Always => true,
 2013            ShowScrollbar::Never => return None,
 2014        };
 2015
 2016        // The horizontal scrollbar is usually slightly offset to align nicely with
 2017        // indent guides. However, this offset is not needed if indent guides are
 2018        // disabled for the current editor.
 2019        let content_offset = self
 2020            .editor
 2021            .read(cx)
 2022            .show_indent_guides
 2023            .is_none_or(|should_show| should_show)
 2024            .then_some(content_offset)
 2025            .unwrap_or_default();
 2026
 2027        Some(EditorScrollbars::from_scrollbar_axes(
 2028            ScrollbarAxes {
 2029                horizontal: scrollbar_settings.axes.horizontal
 2030                    && self.editor.read(cx).show_scrollbars.horizontal,
 2031                vertical: scrollbar_settings.axes.vertical
 2032                    && self.editor.read(cx).show_scrollbars.vertical,
 2033            },
 2034            scrollbar_layout_information,
 2035            content_offset,
 2036            scroll_position,
 2037            self.style.scrollbar_width,
 2038            right_margin,
 2039            editor_width,
 2040            show_scrollbars,
 2041            self.editor.read(cx).scroll_manager.active_scrollbar_state(),
 2042            window,
 2043        ))
 2044    }
 2045
 2046    fn layout_minimap(
 2047        &self,
 2048        snapshot: &EditorSnapshot,
 2049        minimap_width: Pixels,
 2050        scroll_position: gpui::Point<f64>,
 2051        scrollbar_layout_information: &ScrollbarLayoutInformation,
 2052        scrollbar_layout: Option<&EditorScrollbars>,
 2053        window: &mut Window,
 2054        cx: &mut App,
 2055    ) -> Option<MinimapLayout> {
 2056        let minimap_editor = self.editor.read(cx).minimap().cloned()?;
 2057
 2058        let minimap_settings = EditorSettings::get_global(cx).minimap;
 2059
 2060        if minimap_settings.on_active_editor() {
 2061            let active_editor = self.editor.read(cx).workspace().and_then(|ws| {
 2062                ws.read(cx)
 2063                    .active_pane()
 2064                    .read(cx)
 2065                    .active_item()
 2066                    .and_then(|i| i.act_as::<Editor>(cx))
 2067            });
 2068            if active_editor.is_some_and(|e| e != self.editor) {
 2069                return None;
 2070            }
 2071        }
 2072
 2073        if !snapshot.mode.is_full()
 2074            || minimap_width.is_zero()
 2075            || matches!(
 2076                minimap_settings.show,
 2077                ShowMinimap::Auto if scrollbar_layout.is_none_or(|layout| !layout.visible)
 2078            )
 2079        {
 2080            return None;
 2081        }
 2082
 2083        const MINIMAP_AXIS: ScrollbarAxis = ScrollbarAxis::Vertical;
 2084
 2085        let ScrollbarLayoutInformation {
 2086            editor_bounds,
 2087            scroll_range,
 2088            glyph_grid_cell,
 2089        } = scrollbar_layout_information;
 2090
 2091        let line_height = glyph_grid_cell.height;
 2092        let scroll_position = scroll_position.along(MINIMAP_AXIS);
 2093
 2094        let top_right_anchor = scrollbar_layout
 2095            .and_then(|layout| layout.vertical.as_ref())
 2096            .map(|vertical_scrollbar| vertical_scrollbar.hitbox.origin)
 2097            .unwrap_or_else(|| editor_bounds.top_right());
 2098
 2099        let thumb_state = self
 2100            .editor
 2101            .read_with(cx, |editor, _| editor.scroll_manager.minimap_thumb_state());
 2102
 2103        let show_thumb = match minimap_settings.thumb {
 2104            MinimapThumb::Always => true,
 2105            MinimapThumb::Hover => thumb_state.is_some(),
 2106        };
 2107
 2108        let minimap_bounds = Bounds::from_anchor_and_size(
 2109            gpui::Anchor::TopRight,
 2110            top_right_anchor,
 2111            size(minimap_width, editor_bounds.size.height),
 2112        );
 2113        let minimap_line_height = self.get_minimap_line_height(
 2114            minimap_editor
 2115                .read(cx)
 2116                .text_style_refinement
 2117                .as_ref()
 2118                .and_then(|refinement| refinement.font_size)
 2119                .unwrap_or(MINIMAP_FONT_SIZE),
 2120            window,
 2121            cx,
 2122        );
 2123        let minimap_height = minimap_bounds.size.height;
 2124
 2125        let visible_editor_lines = (editor_bounds.size.height / line_height) as f64;
 2126        let total_editor_lines = (scroll_range.height / line_height) as f64;
 2127        let minimap_lines = (minimap_height / minimap_line_height) as f64;
 2128
 2129        let minimap_scroll_top = MinimapLayout::calculate_minimap_top_offset(
 2130            total_editor_lines,
 2131            visible_editor_lines,
 2132            minimap_lines,
 2133            scroll_position,
 2134        );
 2135
 2136        let layout = ScrollbarLayout::for_minimap(
 2137            window.insert_hitbox(minimap_bounds, HitboxBehavior::Normal),
 2138            visible_editor_lines,
 2139            total_editor_lines,
 2140            minimap_line_height,
 2141            scroll_position,
 2142            minimap_scroll_top,
 2143            show_thumb,
 2144        )
 2145        .with_thumb_state(thumb_state);
 2146
 2147        minimap_editor.update(cx, |editor, cx| {
 2148            editor.set_scroll_position(point(0., minimap_scroll_top), window, cx)
 2149        });
 2150
 2151        // Required for the drop shadow to be visible
 2152        const PADDING_OFFSET: Pixels = px(4.);
 2153
 2154        let mut minimap = div()
 2155            .size_full()
 2156            .shadow_xs()
 2157            .px(PADDING_OFFSET)
 2158            .child(minimap_editor)
 2159            .into_any_element();
 2160
 2161        let extended_bounds = minimap_bounds.extend(Edges {
 2162            right: PADDING_OFFSET,
 2163            left: PADDING_OFFSET,
 2164            ..Default::default()
 2165        });
 2166        minimap.layout_as_root(extended_bounds.size.into(), window, cx);
 2167        window.with_absolute_element_offset(extended_bounds.origin, |window| {
 2168            minimap.prepaint(window, cx)
 2169        });
 2170
 2171        Some(MinimapLayout {
 2172            minimap,
 2173            thumb_layout: layout,
 2174            thumb_border_style: minimap_settings.thumb_border,
 2175            minimap_line_height,
 2176            minimap_scroll_top,
 2177            max_scroll_top: total_editor_lines,
 2178        })
 2179    }
 2180
 2181    fn get_minimap_line_height(
 2182        &self,
 2183        font_size: AbsoluteLength,
 2184        window: &mut Window,
 2185        cx: &mut App,
 2186    ) -> Pixels {
 2187        let rem_size = self.rem_size(cx).unwrap_or(window.rem_size());
 2188        let mut text_style = self.style.text.clone();
 2189        text_style.font_size = font_size;
 2190        text_style.line_height_in_pixels(rem_size)
 2191    }
 2192
 2193    fn get_minimap_width(
 2194        &self,
 2195        minimap_settings: &Minimap,
 2196        scrollbars_shown: bool,
 2197        text_width: Pixels,
 2198        em_width: Pixels,
 2199        font_size: Pixels,
 2200        rem_size: Pixels,
 2201        cx: &App,
 2202    ) -> Option<Pixels> {
 2203        if minimap_settings.show == ShowMinimap::Auto && !scrollbars_shown {
 2204            return None;
 2205        }
 2206
 2207        let minimap_font_size = self.editor.read_with(cx, |editor, cx| {
 2208            editor.minimap().map(|minimap_editor| {
 2209                minimap_editor
 2210                    .read(cx)
 2211                    .text_style_refinement
 2212                    .as_ref()
 2213                    .and_then(|refinement| refinement.font_size)
 2214                    .unwrap_or(MINIMAP_FONT_SIZE)
 2215            })
 2216        })?;
 2217
 2218        let minimap_em_width = em_width * (minimap_font_size.to_pixels(rem_size) / font_size);
 2219
 2220        let minimap_width = (text_width * MinimapLayout::MINIMAP_WIDTH_PCT)
 2221            .min(minimap_em_width * minimap_settings.max_width_columns.get() as f32);
 2222
 2223        (minimap_width >= minimap_em_width * MinimapLayout::MINIMAP_MIN_WIDTH_COLUMNS)
 2224            .then_some(minimap_width)
 2225    }
 2226
 2227    fn prepaint_crease_toggles(
 2228        &self,
 2229        crease_toggles: &mut [Option<AnyElement>],
 2230        line_height: Pixels,
 2231        gutter_dimensions: &GutterDimensions,
 2232        gutter_settings: crate::editor_settings::Gutter,
 2233        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 2234        gutter_hitbox: &Hitbox,
 2235        window: &mut Window,
 2236        cx: &mut App,
 2237    ) {
 2238        for (ix, crease_toggle) in crease_toggles.iter_mut().enumerate() {
 2239            if let Some(crease_toggle) = crease_toggle {
 2240                debug_assert!(gutter_settings.folds);
 2241                let available_space = size(
 2242                    AvailableSpace::MinContent,
 2243                    AvailableSpace::Definite(line_height * 0.55),
 2244                );
 2245                let crease_toggle_size = crease_toggle.layout_as_root(available_space, window, cx);
 2246
 2247                let position = point(
 2248                    gutter_dimensions.width - gutter_dimensions.right_padding,
 2249                    ix as f32 * line_height
 2250                        - (scroll_pixel_position.y % ScrollPixelOffset::from(line_height)).into(),
 2251                );
 2252                let centering_offset = point(
 2253                    (gutter_dimensions.fold_area_width() - crease_toggle_size.width) / 2.,
 2254                    (line_height - crease_toggle_size.height) / 2.,
 2255                );
 2256                let origin = gutter_hitbox.origin + position + centering_offset;
 2257                crease_toggle.prepaint_as_root(origin, available_space, window, cx);
 2258            }
 2259        }
 2260    }
 2261
 2262    fn prepaint_expand_toggles(
 2263        &self,
 2264        expand_toggles: &mut [Option<(AnyElement, gpui::Point<Pixels>)>],
 2265        window: &mut Window,
 2266        cx: &mut App,
 2267    ) {
 2268        for (expand_toggle, origin) in expand_toggles.iter_mut().flatten() {
 2269            let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
 2270            expand_toggle.layout_as_root(available_space, window, cx);
 2271            expand_toggle.prepaint_as_root(*origin, available_space, window, cx);
 2272        }
 2273    }
 2274
 2275    fn prepaint_crease_trailers(
 2276        &self,
 2277        trailers: Vec<Option<AnyElement>>,
 2278        lines: &[LineWithInvisibles],
 2279        line_height: Pixels,
 2280        content_origin: gpui::Point<Pixels>,
 2281        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 2282        em_width: Pixels,
 2283        window: &mut Window,
 2284        cx: &mut App,
 2285    ) -> Vec<Option<CreaseTrailerLayout>> {
 2286        trailers
 2287            .into_iter()
 2288            .enumerate()
 2289            .map(|(ix, element)| {
 2290                let mut element = element?;
 2291                let available_space = size(
 2292                    AvailableSpace::MinContent,
 2293                    AvailableSpace::Definite(line_height),
 2294                );
 2295                let size = element.layout_as_root(available_space, window, cx);
 2296
 2297                let line = &lines[ix];
 2298                let padding = if line.width == Pixels::ZERO {
 2299                    Pixels::ZERO
 2300                } else {
 2301                    4. * em_width
 2302                };
 2303                let position = point(
 2304                    Pixels::from(scroll_pixel_position.x) + line.width + padding,
 2305                    ix as f32 * line_height
 2306                        - (scroll_pixel_position.y % ScrollPixelOffset::from(line_height)).into(),
 2307                );
 2308                let centering_offset = point(px(0.), (line_height - size.height) / 2.);
 2309                let origin = content_origin + position + centering_offset;
 2310                element.prepaint_as_root(origin, available_space, window, cx);
 2311                Some(CreaseTrailerLayout {
 2312                    element,
 2313                    bounds: Bounds::new(origin, size),
 2314                })
 2315            })
 2316            .collect()
 2317    }
 2318
 2319    // Folds contained in a hunk are ignored apart from shrinking visual size
 2320    // If a fold contains any hunks then that fold line is marked as modified
 2321    fn layout_gutter_diff_hunks(
 2322        &self,
 2323        line_height: Pixels,
 2324        gutter_hitbox: &Hitbox,
 2325        display_rows: Range<DisplayRow>,
 2326        snapshot: &EditorSnapshot,
 2327        window: &mut Window,
 2328        cx: &mut App,
 2329    ) -> Vec<(DisplayDiffHunk, Option<Hitbox>)> {
 2330        let folded_buffers = self.editor.read(cx).folded_buffers(cx);
 2331        let mut display_hunks = snapshot
 2332            .display_diff_hunks_for_rows(display_rows, folded_buffers)
 2333            .map(|hunk| (hunk, None))
 2334            .collect::<Vec<_>>();
 2335        let git_gutter_setting = ProjectSettings::get_global(cx).git.git_gutter;
 2336        if let GitGutterSetting::TrackedFiles = git_gutter_setting {
 2337            for (hunk, hitbox) in &mut display_hunks {
 2338                if matches!(hunk, DisplayDiffHunk::Unfolded { .. }) {
 2339                    let hunk_bounds =
 2340                        Self::diff_hunk_bounds(snapshot, line_height, gutter_hitbox.bounds, hunk);
 2341                    *hitbox = Some(window.insert_hitbox(hunk_bounds, HitboxBehavior::BlockMouse));
 2342                }
 2343            }
 2344        }
 2345
 2346        display_hunks
 2347    }
 2348
 2349    fn layout_inline_diagnostics(
 2350        &self,
 2351        line_layouts: &[LineWithInvisibles],
 2352        crease_trailers: &[Option<CreaseTrailerLayout>],
 2353        row_block_types: &HashMap<DisplayRow, bool>,
 2354        content_origin: gpui::Point<Pixels>,
 2355        scroll_position: gpui::Point<ScrollOffset>,
 2356        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 2357        edit_prediction_popover_origin: Option<gpui::Point<Pixels>>,
 2358        start_row: DisplayRow,
 2359        end_row: DisplayRow,
 2360        line_height: Pixels,
 2361        em_width: Pixels,
 2362        style: &EditorStyle,
 2363        window: &mut Window,
 2364        cx: &mut App,
 2365    ) -> HashMap<DisplayRow, AnyElement> {
 2366        let max_severity = match self
 2367            .editor
 2368            .read(cx)
 2369            .inline_diagnostics_enabled()
 2370            .then(|| {
 2371                ProjectSettings::get_global(cx)
 2372                    .diagnostics
 2373                    .inline
 2374                    .max_severity
 2375                    .unwrap_or_else(|| self.editor.read(cx).diagnostics_max_severity)
 2376                    .into_lsp()
 2377            })
 2378            .flatten()
 2379        {
 2380            Some(max_severity) => max_severity,
 2381            None => return HashMap::default(),
 2382        };
 2383
 2384        let active_diagnostics_group =
 2385            if let ActiveDiagnostic::Group(group) = &self.editor.read(cx).active_diagnostics {
 2386                Some(group.group_id)
 2387            } else {
 2388                None
 2389            };
 2390
 2391        let diagnostics_by_rows = self.editor.update(cx, |editor, cx| {
 2392            let snapshot = editor.snapshot(window, cx);
 2393            editor
 2394                .inline_diagnostics
 2395                .iter()
 2396                .filter(|(_, diagnostic)| diagnostic.severity <= max_severity)
 2397                .filter(|(_, diagnostic)| match active_diagnostics_group {
 2398                    Some(active_diagnostics_group) => {
 2399                        // Active diagnostics are all shown in the editor already, no need to display them inline
 2400                        diagnostic.group_id != active_diagnostics_group
 2401                    }
 2402                    None => true,
 2403                })
 2404                .map(|(point, diag)| (point.to_display_point(&snapshot), diag.clone()))
 2405                .skip_while(|(point, _)| point.row() < start_row)
 2406                .take_while(|(point, _)| point.row() < end_row)
 2407                .filter(|(point, _)| !row_block_types.contains_key(&point.row()))
 2408                .fold(HashMap::default(), |mut acc, (point, diagnostic)| {
 2409                    acc.entry(point.row())
 2410                        .or_insert_with(Vec::new)
 2411                        .push(diagnostic);
 2412                    acc
 2413                })
 2414        });
 2415
 2416        if diagnostics_by_rows.is_empty() {
 2417            return HashMap::default();
 2418        }
 2419
 2420        let severity_to_color = |sev: &lsp::DiagnosticSeverity| match sev {
 2421            &lsp::DiagnosticSeverity::ERROR => Color::Error,
 2422            &lsp::DiagnosticSeverity::WARNING => Color::Warning,
 2423            &lsp::DiagnosticSeverity::INFORMATION => Color::Info,
 2424            &lsp::DiagnosticSeverity::HINT => Color::Hint,
 2425            _ => Color::Error,
 2426        };
 2427
 2428        let padding = ProjectSettings::get_global(cx).diagnostics.inline.padding as f32 * em_width;
 2429        let min_x = column_pixels(
 2430            &self.style,
 2431            ProjectSettings::get_global(cx)
 2432                .diagnostics
 2433                .inline
 2434                .min_column as usize,
 2435            window,
 2436        );
 2437
 2438        let mut elements = HashMap::default();
 2439        for (row, mut diagnostics) in diagnostics_by_rows {
 2440            diagnostics.sort_by_key(|diagnostic| {
 2441                (
 2442                    diagnostic.severity,
 2443                    std::cmp::Reverse(diagnostic.is_primary),
 2444                    diagnostic.start.row,
 2445                    diagnostic.start.column,
 2446                )
 2447            });
 2448
 2449            let Some(diagnostic_to_render) = diagnostics
 2450                .iter()
 2451                .find(|diagnostic| diagnostic.is_primary)
 2452                .or_else(|| diagnostics.first())
 2453            else {
 2454                continue;
 2455            };
 2456
 2457            let pos_y = content_origin.y + line_height * (row.0 as f64 - scroll_position.y) as f32;
 2458
 2459            let window_ix = row.0.saturating_sub(start_row.0) as usize;
 2460            let pos_x = {
 2461                let crease_trailer_layout = &crease_trailers[window_ix];
 2462                let line_layout = &line_layouts[window_ix];
 2463
 2464                let line_end = if let Some(crease_trailer) = crease_trailer_layout {
 2465                    crease_trailer.bounds.right()
 2466                } else {
 2467                    Pixels::from(
 2468                        ScrollPixelOffset::from(content_origin.x + line_layout.width)
 2469                            - scroll_pixel_position.x,
 2470                    )
 2471                };
 2472
 2473                let padded_line = line_end + padding;
 2474                let min_start = Pixels::from(
 2475                    ScrollPixelOffset::from(content_origin.x + min_x) - scroll_pixel_position.x,
 2476                );
 2477
 2478                cmp::max(padded_line, min_start)
 2479            };
 2480
 2481            let behind_edit_prediction_popover = edit_prediction_popover_origin
 2482                .as_ref()
 2483                .is_some_and(|edit_prediction_popover_origin| {
 2484                    (pos_y..pos_y + line_height).contains(&edit_prediction_popover_origin.y)
 2485                });
 2486            let opacity = if behind_edit_prediction_popover {
 2487                0.5
 2488            } else {
 2489                1.0
 2490            };
 2491
 2492            let mut element = h_flex()
 2493                .id(("diagnostic", row.0))
 2494                .h(line_height)
 2495                .w_full()
 2496                .px_1()
 2497                .rounded_xs()
 2498                .opacity(opacity)
 2499                .bg(severity_to_color(&diagnostic_to_render.severity)
 2500                    .color(cx)
 2501                    .opacity(0.05))
 2502                .text_color(severity_to_color(&diagnostic_to_render.severity).color(cx))
 2503                .text_sm()
 2504                .font(style.text.font())
 2505                .child(diagnostic_to_render.message.clone())
 2506                .into_any();
 2507
 2508            element.prepaint_as_root(point(pos_x, pos_y), AvailableSpace::min_size(), window, cx);
 2509
 2510            elements.insert(row, element);
 2511        }
 2512
 2513        elements
 2514    }
 2515
 2516    fn layout_inline_code_actions(
 2517        &self,
 2518        display_point: DisplayPoint,
 2519        content_origin: gpui::Point<Pixels>,
 2520        scroll_position: gpui::Point<ScrollOffset>,
 2521        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 2522        line_height: Pixels,
 2523        snapshot: &EditorSnapshot,
 2524        window: &mut Window,
 2525        cx: &mut App,
 2526    ) -> Option<AnyElement> {
 2527        // Don't show code actions in split diff view
 2528        if self.split_side.is_some() {
 2529            return None;
 2530        }
 2531
 2532        if !snapshot
 2533            .show_code_actions
 2534            .unwrap_or(EditorSettings::get_global(cx).inline_code_actions)
 2535        {
 2536            return None;
 2537        }
 2538
 2539        let icon_size = ui::IconSize::XSmall;
 2540        let mut button = self.editor.update(cx, |editor, cx| {
 2541            editor.available_code_actions.as_ref()?;
 2542            let active = editor
 2543                .context_menu
 2544                .borrow()
 2545                .as_ref()
 2546                .and_then(|menu| {
 2547                    if let crate::CodeContextMenu::CodeActions(CodeActionsMenu {
 2548                        deployed_from,
 2549                        ..
 2550                    }) = menu
 2551                    {
 2552                        deployed_from.as_ref()
 2553                    } else {
 2554                        None
 2555                    }
 2556                })
 2557                .is_some_and(|source| matches!(source, CodeActionSource::Indicator(..)));
 2558            Some(editor.render_inline_code_actions(icon_size, display_point.row(), active, cx))
 2559        })?;
 2560
 2561        let buffer_point = display_point.to_point(&snapshot.display_snapshot);
 2562
 2563        // do not show code action for folded line
 2564        if snapshot.is_line_folded(MultiBufferRow(buffer_point.row)) {
 2565            return None;
 2566        }
 2567
 2568        // do not show code action for blank line with cursor
 2569        let line_indent = snapshot
 2570            .display_snapshot
 2571            .buffer_snapshot()
 2572            .line_indent_for_row(MultiBufferRow(buffer_point.row));
 2573        if line_indent.is_line_blank() {
 2574            return None;
 2575        }
 2576
 2577        const INLINE_SLOT_CHAR_LIMIT: u32 = 4;
 2578        const MAX_ALTERNATE_DISTANCE: u32 = 8;
 2579
 2580        let is_valid_row = |row_candidate: u32| -> bool {
 2581            // move to other row if folded row
 2582            if snapshot.is_line_folded(MultiBufferRow(row_candidate)) {
 2583                return false;
 2584            }
 2585            if buffer_point.row == row_candidate {
 2586                // move to other row if cursor is in slot
 2587                if buffer_point.column < INLINE_SLOT_CHAR_LIMIT {
 2588                    return false;
 2589                }
 2590            } else {
 2591                let candidate_point = MultiBufferPoint {
 2592                    row: row_candidate,
 2593                    column: 0,
 2594                };
 2595                // move to other row if different excerpt
 2596                let range = if candidate_point < buffer_point {
 2597                    candidate_point..buffer_point
 2598                } else {
 2599                    buffer_point..candidate_point
 2600                };
 2601                if snapshot
 2602                    .display_snapshot
 2603                    .buffer_snapshot()
 2604                    .excerpt_containing(range)
 2605                    .is_none()
 2606                {
 2607                    return false;
 2608                }
 2609            }
 2610            let line_indent = snapshot
 2611                .display_snapshot
 2612                .buffer_snapshot()
 2613                .line_indent_for_row(MultiBufferRow(row_candidate));
 2614            // use this row if it's blank
 2615            if line_indent.is_line_blank() {
 2616                true
 2617            } else {
 2618                // use this row if code starts after slot
 2619                let indent_size = snapshot
 2620                    .display_snapshot
 2621                    .buffer_snapshot()
 2622                    .indent_size_for_line(MultiBufferRow(row_candidate));
 2623                indent_size.len >= INLINE_SLOT_CHAR_LIMIT
 2624            }
 2625        };
 2626
 2627        let new_buffer_row = if is_valid_row(buffer_point.row) {
 2628            Some(buffer_point.row)
 2629        } else {
 2630            let max_row = snapshot.display_snapshot.buffer_snapshot().max_point().row;
 2631            (1..=MAX_ALTERNATE_DISTANCE).find_map(|offset| {
 2632                let row_above = buffer_point.row.saturating_sub(offset);
 2633                let row_below = buffer_point.row + offset;
 2634                if row_above != buffer_point.row && is_valid_row(row_above) {
 2635                    Some(row_above)
 2636                } else if row_below <= max_row && is_valid_row(row_below) {
 2637                    Some(row_below)
 2638                } else {
 2639                    None
 2640                }
 2641            })
 2642        }?;
 2643
 2644        let new_display_row = snapshot
 2645            .display_snapshot
 2646            .point_to_display_point(
 2647                Point {
 2648                    row: new_buffer_row,
 2649                    column: buffer_point.column,
 2650                },
 2651                text::Bias::Left,
 2652            )
 2653            .row();
 2654
 2655        let start_y = content_origin.y
 2656            + (((new_display_row.as_f64() - scroll_position.y) as f32) * line_height)
 2657            + (line_height / 2.0)
 2658            - (icon_size.square(window, cx) / 2.);
 2659        let start_x = (ScrollPixelOffset::from(content_origin.x) - scroll_pixel_position.x
 2660            + ScrollPixelOffset::from(window.rem_size() * 0.1))
 2661        .into();
 2662
 2663        let absolute_offset = gpui::point(start_x, start_y);
 2664        button.layout_as_root(gpui::AvailableSpace::min_size(), window, cx);
 2665        button.prepaint_as_root(
 2666            absolute_offset,
 2667            gpui::AvailableSpace::min_size(),
 2668            window,
 2669            cx,
 2670        );
 2671        Some(button)
 2672    }
 2673
 2674    fn layout_inline_blame(
 2675        &self,
 2676        display_row: DisplayRow,
 2677        row_info: &RowInfo,
 2678        line_layout: &LineWithInvisibles,
 2679        crease_trailer: Option<&CreaseTrailerLayout>,
 2680        em_width: Pixels,
 2681        content_origin: gpui::Point<Pixels>,
 2682        scroll_position: gpui::Point<ScrollOffset>,
 2683        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 2684        line_height: Pixels,
 2685        window: &mut Window,
 2686        cx: &mut App,
 2687    ) -> Option<InlineBlameLayout> {
 2688        if !self
 2689            .editor
 2690            .update(cx, |editor, cx| editor.render_git_blame_inline(window, cx))
 2691        {
 2692            return None;
 2693        }
 2694
 2695        let editor = self.editor.read(cx);
 2696        let blame = editor.blame.clone()?;
 2697        let padding = {
 2698            const INLINE_ACCEPT_SUGGESTION_EM_WIDTHS: f32 = 14.;
 2699
 2700            let mut padding = ProjectSettings::get_global(cx).git.inline_blame.padding as f32;
 2701
 2702            if let Some(edit_prediction) = editor.active_edit_prediction.as_ref()
 2703                && let EditPrediction::Edit {
 2704                    display_mode: EditDisplayMode::TabAccept,
 2705                    ..
 2706                } = &edit_prediction.completion
 2707            {
 2708                padding += INLINE_ACCEPT_SUGGESTION_EM_WIDTHS
 2709            }
 2710
 2711            padding * em_width
 2712        };
 2713
 2714        let (buffer_id, entry) = blame
 2715            .update(cx, |blame, cx| {
 2716                blame.blame_for_rows(&[*row_info], cx).next()
 2717            })
 2718            .flatten()?;
 2719
 2720        let mut element = render_inline_blame_entry(entry.clone(), &self.style, cx)?;
 2721
 2722        let start_y =
 2723            content_origin.y + line_height * ((display_row.as_f64() - scroll_position.y) as f32);
 2724
 2725        let start_x = {
 2726            let line_end = if let Some(crease_trailer) = crease_trailer {
 2727                crease_trailer.bounds.right()
 2728            } else {
 2729                Pixels::from(
 2730                    ScrollPixelOffset::from(content_origin.x + line_layout.width)
 2731                        - scroll_pixel_position.x,
 2732                )
 2733            };
 2734
 2735            let padded_line_end = line_end + padding;
 2736
 2737            let min_column_in_pixels = column_pixels(
 2738                &self.style,
 2739                ProjectSettings::get_global(cx).git.inline_blame.min_column as usize,
 2740                window,
 2741            );
 2742            let min_start = Pixels::from(
 2743                ScrollPixelOffset::from(content_origin.x + min_column_in_pixels)
 2744                    - scroll_pixel_position.x,
 2745            );
 2746
 2747            cmp::max(padded_line_end, min_start)
 2748        };
 2749
 2750        let absolute_offset = point(start_x, start_y);
 2751        let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
 2752        let bounds = Bounds::new(absolute_offset, size);
 2753
 2754        element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), window, cx);
 2755
 2756        Some(InlineBlameLayout {
 2757            element,
 2758            bounds,
 2759            buffer_id,
 2760            entry,
 2761        })
 2762    }
 2763
 2764    fn layout_blame_popover(
 2765        &self,
 2766        editor_snapshot: &EditorSnapshot,
 2767        text_hitbox: &Hitbox,
 2768        line_height: Pixels,
 2769        window: &mut Window,
 2770        cx: &mut App,
 2771    ) {
 2772        if !self.editor.read(cx).inline_blame_popover.is_some() {
 2773            return;
 2774        }
 2775
 2776        let Some(blame) = self.editor.read(cx).blame.clone() else {
 2777            return;
 2778        };
 2779        let cursor_point = self
 2780            .editor
 2781            .read(cx)
 2782            .selections
 2783            .newest::<language::Point>(&editor_snapshot.display_snapshot)
 2784            .head();
 2785
 2786        let Some((buffer, buffer_point)) = editor_snapshot
 2787            .buffer_snapshot()
 2788            .point_to_buffer_point(cursor_point)
 2789        else {
 2790            return;
 2791        };
 2792
 2793        let row_info = RowInfo {
 2794            buffer_id: Some(buffer.remote_id()),
 2795            buffer_row: Some(buffer_point.row),
 2796            ..Default::default()
 2797        };
 2798
 2799        let Some((buffer_id, blame_entry)) = blame
 2800            .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
 2801            .flatten()
 2802        else {
 2803            return;
 2804        };
 2805
 2806        let Some((popover_state, target_point)) = self.editor.read_with(cx, |editor, _| {
 2807            editor
 2808                .inline_blame_popover
 2809                .as_ref()
 2810                .map(|state| (state.popover_state.clone(), state.position))
 2811        }) else {
 2812            return;
 2813        };
 2814
 2815        let workspace = self
 2816            .editor
 2817            .read_with(cx, |editor, _| editor.workspace().map(|w| w.downgrade()));
 2818
 2819        let maybe_element = workspace.and_then(|workspace| {
 2820            render_blame_entry_popover(
 2821                blame_entry,
 2822                popover_state.scroll_handle,
 2823                popover_state.commit_message,
 2824                popover_state.markdown,
 2825                workspace,
 2826                &blame,
 2827                buffer_id,
 2828                window,
 2829                cx,
 2830            )
 2831        });
 2832
 2833        if let Some(mut element) = maybe_element {
 2834            let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
 2835            let overall_height = size.height + HOVER_POPOVER_GAP;
 2836            let popover_origin = if target_point.y > overall_height {
 2837                point(target_point.x, target_point.y - size.height)
 2838            } else {
 2839                point(
 2840                    target_point.x,
 2841                    target_point.y + line_height + HOVER_POPOVER_GAP,
 2842                )
 2843            };
 2844
 2845            let horizontal_offset = (text_hitbox.top_right().x
 2846                - POPOVER_RIGHT_OFFSET
 2847                - (popover_origin.x + size.width))
 2848                .min(Pixels::ZERO);
 2849
 2850            let origin = point(popover_origin.x + horizontal_offset, popover_origin.y);
 2851            let popover_bounds = Bounds::new(origin, size);
 2852
 2853            self.editor.update(cx, |editor, _| {
 2854                if let Some(state) = &mut editor.inline_blame_popover {
 2855                    state.popover_bounds = Some(popover_bounds);
 2856                }
 2857            });
 2858
 2859            window.defer_draw(element, origin, 2, None);
 2860        }
 2861    }
 2862
 2863    fn layout_blame_entries(
 2864        &self,
 2865        buffer_rows: &[RowInfo],
 2866        em_width: Pixels,
 2867        scroll_position: gpui::Point<ScrollOffset>,
 2868        line_height: Pixels,
 2869        gutter_hitbox: &Hitbox,
 2870        max_width: Option<Pixels>,
 2871        window: &mut Window,
 2872        cx: &mut App,
 2873    ) -> Option<Vec<AnyElement>> {
 2874        if !self
 2875            .editor
 2876            .update(cx, |editor, cx| editor.render_git_blame_gutter(cx))
 2877        {
 2878            return None;
 2879        }
 2880
 2881        let blame = self.editor.read(cx).blame.clone()?;
 2882        let workspace = self.editor.read(cx).workspace()?;
 2883        let blamed_rows: Vec<_> = blame.update(cx, |blame, cx| {
 2884            blame.blame_for_rows(buffer_rows, cx).collect()
 2885        });
 2886
 2887        let width = if let Some(max_width) = max_width {
 2888            AvailableSpace::Definite(max_width)
 2889        } else {
 2890            AvailableSpace::MaxContent
 2891        };
 2892        let scroll_top = scroll_position.y * ScrollPixelOffset::from(line_height);
 2893        let start_x = em_width;
 2894
 2895        let mut last_used_color: Option<(Hsla, Oid)> = None;
 2896        let blame_renderer = cx.global::<GlobalBlameRenderer>().0.clone();
 2897
 2898        let shaped_lines = blamed_rows
 2899            .into_iter()
 2900            .enumerate()
 2901            .flat_map(|(ix, blame_entry)| {
 2902                let (buffer_id, blame_entry) = blame_entry?;
 2903                let mut element = render_blame_entry(
 2904                    ix,
 2905                    &blame,
 2906                    blame_entry,
 2907                    &self.style,
 2908                    &mut last_used_color,
 2909                    self.editor.clone(),
 2910                    workspace.clone(),
 2911                    buffer_id,
 2912                    &*blame_renderer,
 2913                    window,
 2914                    cx,
 2915                )?;
 2916
 2917                let start_y = ix as f32 * line_height
 2918                    - Pixels::from(scroll_top % ScrollPixelOffset::from(line_height));
 2919                let absolute_offset = gutter_hitbox.origin + point(start_x, start_y);
 2920
 2921                element.prepaint_as_root(
 2922                    absolute_offset,
 2923                    size(width, AvailableSpace::MinContent),
 2924                    window,
 2925                    cx,
 2926                );
 2927
 2928                Some(element)
 2929            })
 2930            .collect();
 2931
 2932        Some(shaped_lines)
 2933    }
 2934
 2935    fn layout_indent_guides(
 2936        &self,
 2937        content_origin: gpui::Point<Pixels>,
 2938        text_origin: gpui::Point<Pixels>,
 2939        visible_buffer_range: Range<MultiBufferRow>,
 2940        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 2941        line_height: Pixels,
 2942        snapshot: &DisplaySnapshot,
 2943        window: &mut Window,
 2944        cx: &mut App,
 2945    ) -> Option<Vec<IndentGuideLayout>> {
 2946        let indent_guides = self.editor.update(cx, |editor, cx| {
 2947            editor.indent_guides(visible_buffer_range, snapshot, cx)
 2948        })?;
 2949
 2950        let active_indent_guide_indices = self.editor.update(cx, |editor, cx| {
 2951            editor
 2952                .find_active_indent_guide_indices(&indent_guides, snapshot, window, cx)
 2953                .unwrap_or_default()
 2954        });
 2955
 2956        Some(
 2957            indent_guides
 2958                .into_iter()
 2959                .enumerate()
 2960                .filter_map(|(i, indent_guide)| {
 2961                    let single_indent_width =
 2962                        column_pixels(&self.style, indent_guide.tab_size as usize, window);
 2963                    let total_width = single_indent_width * indent_guide.depth as f32;
 2964                    let start_x = Pixels::from(
 2965                        ScrollOffset::from(content_origin.x + total_width)
 2966                            - scroll_pixel_position.x,
 2967                    );
 2968                    if start_x >= text_origin.x {
 2969                        let (offset_y, length, display_row_range) =
 2970                            Self::calculate_indent_guide_bounds(
 2971                                indent_guide.start_row..indent_guide.end_row,
 2972                                line_height,
 2973                                snapshot,
 2974                            );
 2975
 2976                        let start_y = Pixels::from(
 2977                            ScrollOffset::from(content_origin.y) + offset_y
 2978                                - scroll_pixel_position.y,
 2979                        );
 2980
 2981                        Some(IndentGuideLayout {
 2982                            origin: point(start_x, start_y),
 2983                            length,
 2984                            single_indent_width,
 2985                            display_row_range,
 2986                            depth: indent_guide.depth,
 2987                            active: active_indent_guide_indices.contains(&i),
 2988                            settings: indent_guide.settings,
 2989                        })
 2990                    } else {
 2991                        None
 2992                    }
 2993                })
 2994                .collect(),
 2995        )
 2996    }
 2997
 2998    fn depth_zero_indent_guide_padding_for_row(
 2999        indent_guides: &[IndentGuideLayout],
 3000        row: DisplayRow,
 3001    ) -> Pixels {
 3002        indent_guides
 3003            .iter()
 3004            .find(|guide| guide.depth == 0 && guide.display_row_range.contains(&row))
 3005            .and_then(|guide| {
 3006                guide
 3007                    .settings
 3008                    .visible_line_width(guide.active)
 3009                    .map(|width| px(width as f32 * 2.0))
 3010            })
 3011            .unwrap_or(px(0.0))
 3012    }
 3013
 3014    fn layout_wrap_guides(
 3015        &self,
 3016        em_advance: Pixels,
 3017        scroll_position: gpui::Point<f64>,
 3018        content_origin: gpui::Point<Pixels>,
 3019        scrollbar_layout: Option<&EditorScrollbars>,
 3020        vertical_scrollbar_width: Pixels,
 3021        hitbox: &Hitbox,
 3022        window: &Window,
 3023        cx: &App,
 3024    ) -> SmallVec<[(Pixels, bool); 2]> {
 3025        let scroll_left = scroll_position.x as f32 * em_advance;
 3026        let content_origin = content_origin.x;
 3027        let horizontal_offset = content_origin - scroll_left;
 3028        let vertical_scrollbar_width = scrollbar_layout
 3029            .and_then(|layout| layout.visible.then_some(vertical_scrollbar_width))
 3030            .unwrap_or_default();
 3031
 3032        self.editor
 3033            .read(cx)
 3034            .wrap_guides(cx)
 3035            .into_iter()
 3036            .flat_map(|(guide, active)| {
 3037                let wrap_position = column_pixels(&self.style, guide, window);
 3038                let wrap_guide_x = wrap_position + horizontal_offset;
 3039                let display_wrap_guide = wrap_guide_x >= content_origin
 3040                    && wrap_guide_x <= hitbox.bounds.right() - vertical_scrollbar_width;
 3041
 3042                display_wrap_guide.then_some((wrap_guide_x, active))
 3043            })
 3044            .collect()
 3045    }
 3046
 3047    fn calculate_indent_guide_bounds(
 3048        row_range: Range<MultiBufferRow>,
 3049        line_height: Pixels,
 3050        snapshot: &DisplaySnapshot,
 3051    ) -> (f64, gpui::Pixels, Range<DisplayRow>) {
 3052        let start_point = Point::new(row_range.start.0, 0);
 3053        let end_point = Point::new(row_range.end.0, 0);
 3054
 3055        let mut row_range = start_point.to_display_point(snapshot).row()
 3056            ..end_point.to_display_point(snapshot).row();
 3057
 3058        let mut prev_line = start_point;
 3059        prev_line.row = prev_line.row.saturating_sub(1);
 3060        let prev_line = prev_line.to_display_point(snapshot).row();
 3061
 3062        let mut cons_line = end_point;
 3063        cons_line.row += 1;
 3064        let cons_line = cons_line.to_display_point(snapshot).row();
 3065
 3066        let mut offset_y = row_range.start.as_f64() * f64::from(line_height);
 3067        let mut length = (cons_line.0.saturating_sub(row_range.start.0)) as f32 * line_height;
 3068
 3069        // If we are at the end of the buffer, ensure that the indent guide extends to the end of the line.
 3070        if row_range.end == cons_line {
 3071            length += line_height;
 3072        }
 3073
 3074        // If there is a block (e.g. diagnostic) in between the start of the indent guide and the line above,
 3075        // we want to extend the indent guide to the start of the block.
 3076        let mut block_height = 0;
 3077        let mut block_offset = 0;
 3078        let mut found_excerpt_header = false;
 3079        for (_, block) in snapshot.blocks_in_range(prev_line..row_range.start) {
 3080            if matches!(
 3081                block,
 3082                Block::ExcerptBoundary { .. } | Block::BufferHeader { .. }
 3083            ) {
 3084                found_excerpt_header = true;
 3085                break;
 3086            }
 3087            block_offset += block.height();
 3088            block_height += block.height();
 3089        }
 3090        if !found_excerpt_header {
 3091            offset_y -= block_offset as f64 * f64::from(line_height);
 3092            length += block_height as f32 * line_height;
 3093            row_range = DisplayRow(row_range.start.0.saturating_sub(block_offset))..row_range.end;
 3094        }
 3095
 3096        // If there is a block (e.g. diagnostic) at the end of an multibuffer excerpt,
 3097        // we want to ensure that the indent guide stops before the excerpt header.
 3098        let mut block_height = 0;
 3099        let mut found_excerpt_header = false;
 3100        for (_, block) in snapshot.blocks_in_range(row_range.end..cons_line) {
 3101            if matches!(
 3102                block,
 3103                Block::ExcerptBoundary { .. } | Block::BufferHeader { .. }
 3104            ) {
 3105                found_excerpt_header = true;
 3106            }
 3107            block_height += block.height();
 3108        }
 3109        if found_excerpt_header {
 3110            length -= block_height as f32 * line_height;
 3111        } else {
 3112            row_range = row_range.start..cons_line;
 3113        }
 3114
 3115        (offset_y, length, row_range)
 3116    }
 3117
 3118    fn layout_bookmarks(
 3119        &self,
 3120        gutter: &Gutter<'_>,
 3121        bookmarks: &HashSet<DisplayRow>,
 3122        window: &mut Window,
 3123        cx: &mut App,
 3124    ) -> Vec<AnyElement> {
 3125        if self.split_side == Some(SplitSide::Left) {
 3126            return Vec::new();
 3127        }
 3128
 3129        self.editor.update(cx, |editor, cx| {
 3130            bookmarks
 3131                .iter()
 3132                .filter_map(|row| {
 3133                    gutter.layout_item_skipping_folds(
 3134                        *row,
 3135                        |cx, _| editor.render_bookmark(*row, cx).into_any_element(),
 3136                        window,
 3137                        cx,
 3138                    )
 3139                })
 3140                .collect_vec()
 3141        })
 3142    }
 3143
 3144    fn layout_gutter_hover_button(
 3145        &self,
 3146        gutter: &Gutter,
 3147        position: Anchor,
 3148        row: DisplayRow,
 3149        window: &mut Window,
 3150        cx: &mut App,
 3151    ) -> Option<AnyElement> {
 3152        if self.split_side == Some(SplitSide::Left) {
 3153            return None;
 3154        }
 3155
 3156        self.editor.update(cx, |editor, cx| {
 3157            gutter.layout_item_skipping_folds(
 3158                row,
 3159                |cx, window| {
 3160                    editor
 3161                        .render_gutter_hover_button(position, row, window, cx)
 3162                        .into_any_element()
 3163                },
 3164                window,
 3165                cx,
 3166            )
 3167        })
 3168    }
 3169
 3170    fn layout_breakpoints(
 3171        &self,
 3172        gutter: &Gutter,
 3173        breakpoints: &HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)>,
 3174        window: &mut Window,
 3175        cx: &mut App,
 3176    ) -> Vec<AnyElement> {
 3177        if self.split_side == Some(SplitSide::Left) {
 3178            return Vec::new();
 3179        }
 3180
 3181        self.editor.update(cx, |editor, cx| {
 3182            breakpoints
 3183                .iter()
 3184                .filter_map(|(row, (text_anchor, bp, state))| {
 3185                    gutter.layout_item_skipping_folds(
 3186                        *row,
 3187                        |cx, _| {
 3188                            editor
 3189                                .render_breakpoint(*text_anchor, *row, &bp, *state, cx)
 3190                                .into_any_element()
 3191                        },
 3192                        window,
 3193                        cx,
 3194                    )
 3195                })
 3196                .collect_vec()
 3197        })
 3198    }
 3199
 3200    fn should_render_diff_review_button(
 3201        &self,
 3202        range: Range<DisplayRow>,
 3203        row_infos: &[RowInfo],
 3204        snapshot: &EditorSnapshot,
 3205        cx: &App,
 3206    ) -> Option<(DisplayRow, Option<u32>)> {
 3207        if !cx.has_flag::<DiffReviewFeatureFlag>() {
 3208            return None;
 3209        }
 3210
 3211        let show_diff_review_button = self.editor.read(cx).show_diff_review_button();
 3212        if !show_diff_review_button {
 3213            return None;
 3214        }
 3215
 3216        let indicator = self.editor.read(cx).gutter_diff_review_indicator.0?;
 3217        if !indicator.is_active {
 3218            return None;
 3219        }
 3220
 3221        let display_row = indicator
 3222            .start
 3223            .to_display_point(&snapshot.display_snapshot)
 3224            .row();
 3225        let row_index = (display_row.0.saturating_sub(range.start.0)) as usize;
 3226
 3227        let row_info = row_infos.get(row_index);
 3228        if row_info.is_some_and(|row_info| row_info.expand_info.is_some()) {
 3229            return None;
 3230        }
 3231
 3232        let buffer_id = row_info.and_then(|info| info.buffer_id);
 3233        if buffer_id.is_none() {
 3234            return None;
 3235        }
 3236
 3237        let editor = self.editor.read(cx);
 3238        if buffer_id.is_some_and(|buffer_id| editor.is_buffer_folded(buffer_id, cx)) {
 3239            return None;
 3240        }
 3241
 3242        let buffer_row = row_info.and_then(|info| info.buffer_row);
 3243        Some((display_row, buffer_row))
 3244    }
 3245
 3246    fn layout_run_indicators(
 3247        &self,
 3248        gutter: &Gutter,
 3249        run_indicators: &HashSet<DisplayRow>,
 3250        breakpoints: &HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)>,
 3251        window: &mut Window,
 3252        cx: &mut App,
 3253    ) -> Vec<AnyElement> {
 3254        if self.split_side == Some(SplitSide::Left) {
 3255            return Vec::new();
 3256        }
 3257
 3258        self.editor.update(cx, |editor, cx| {
 3259            let active_task_indicator_row =
 3260                // TODO: add edit button on the right side of each row in the context menu
 3261                if let Some(crate::CodeContextMenu::CodeActions(CodeActionsMenu {
 3262                    deployed_from,
 3263                    actions,
 3264                    ..
 3265                })) = editor.context_menu.borrow().as_ref()
 3266                {
 3267                    actions
 3268                        .tasks()
 3269                        .map(|tasks| tasks.position.to_display_point(gutter.snapshot).row())
 3270                        .or_else(|| match deployed_from {
 3271                            Some(CodeActionSource::Indicator(row)) => Some(*row),
 3272                            _ => None,
 3273                        })
 3274                } else {
 3275                    None
 3276                };
 3277
 3278            run_indicators
 3279                .iter()
 3280                .filter_map(|display_row| {
 3281                    gutter.layout_item(
 3282                        *display_row,
 3283                        |cx, _| {
 3284                            editor
 3285                                .render_run_indicator(
 3286                                    &self.style,
 3287                                    Some(*display_row) == active_task_indicator_row,
 3288                                    breakpoints.get(&display_row).map(|(anchor, _, _)| *anchor),
 3289                                    *display_row,
 3290                                    cx,
 3291                                )
 3292                                .into_any_element()
 3293                        },
 3294                        window,
 3295                        cx,
 3296                    )
 3297                })
 3298                .collect_vec()
 3299        })
 3300    }
 3301
 3302    fn layout_expand_toggles(
 3303        &self,
 3304        gutter_hitbox: &Hitbox,
 3305        gutter_dimensions: GutterDimensions,
 3306        em_width: Pixels,
 3307        line_height: Pixels,
 3308        scroll_position: gpui::Point<ScrollOffset>,
 3309        buffer_rows: &[RowInfo],
 3310        window: &mut Window,
 3311        cx: &mut App,
 3312    ) -> Vec<Option<(AnyElement, gpui::Point<Pixels>)>> {
 3313        if self.editor.read(cx).disable_expand_excerpt_buttons {
 3314            return vec![];
 3315        }
 3316
 3317        let editor_font_size = self.style.text.font_size.to_pixels(window.rem_size()) * 1.2;
 3318
 3319        let scroll_top = scroll_position.y * ScrollPixelOffset::from(line_height);
 3320
 3321        let max_line_number_length = self
 3322            .editor
 3323            .read(cx)
 3324            .buffer()
 3325            .read(cx)
 3326            .snapshot(cx)
 3327            .widest_line_number()
 3328            .ilog10()
 3329            + 1;
 3330
 3331        let git_gutter_width = Self::gutter_strip_width(line_height)
 3332            + gutter_dimensions
 3333                .git_blame_entries_width
 3334                .unwrap_or_default();
 3335        let available_width = gutter_dimensions.left_padding - git_gutter_width;
 3336
 3337        buffer_rows
 3338            .iter()
 3339            .enumerate()
 3340            .map(|(ix, row_info)| {
 3341                let ExpandInfo {
 3342                    direction,
 3343                    start_anchor,
 3344                } = row_info.expand_info?;
 3345
 3346                let icon_name = match direction {
 3347                    ExpandExcerptDirection::Up => IconName::ExpandUp,
 3348                    ExpandExcerptDirection::Down => IconName::ExpandDown,
 3349                    ExpandExcerptDirection::UpAndDown => IconName::ExpandVertical,
 3350                };
 3351
 3352                let editor = self.editor.clone();
 3353                let is_wide = max_line_number_length
 3354                    >= EditorSettings::get_global(cx).gutter.min_line_number_digits as u32
 3355                    && row_info
 3356                        .buffer_row
 3357                        .is_some_and(|row| (row + 1).ilog10() + 1 == max_line_number_length)
 3358                    || gutter_dimensions.right_padding == px(0.);
 3359
 3360                let width = if is_wide {
 3361                    available_width - px(5.)
 3362                } else {
 3363                    available_width + em_width - px(5.)
 3364                };
 3365
 3366                let toggle = IconButton::new(("expand", ix), icon_name)
 3367                    .icon_color(Color::Custom(cx.theme().colors().editor_line_number))
 3368                    .icon_size(IconSize::Custom(rems(editor_font_size / window.rem_size())))
 3369                    .width(width)
 3370                    .on_click(move |_, window, cx| {
 3371                        editor.update(cx, |editor, cx| {
 3372                            editor.expand_excerpt(start_anchor, direction, window, cx);
 3373                        });
 3374                    })
 3375                    .tooltip(Tooltip::for_action_title(
 3376                        "Expand Excerpt",
 3377                        &crate::actions::ExpandExcerpts::default(),
 3378                    ))
 3379                    .into_any_element();
 3380
 3381                let position = point(
 3382                    git_gutter_width + px(1.),
 3383                    ix as f32 * line_height
 3384                        - Pixels::from(scroll_top % ScrollPixelOffset::from(line_height))
 3385                        + px(1.),
 3386                );
 3387                let origin = gutter_hitbox.origin + position;
 3388
 3389                Some((toggle, origin))
 3390            })
 3391            .collect()
 3392    }
 3393
 3394    fn layout_line_numbers(
 3395        &self,
 3396        gutter: &Gutter<'_>,
 3397        active_rows: &BTreeMap<DisplayRow, LineHighlightSpec>,
 3398        current_selection_head: Option<DisplayRow>,
 3399        window: &mut Window,
 3400        cx: &mut App,
 3401    ) -> Arc<HashMap<MultiBufferRow, LineNumberLayout>> {
 3402        let include_line_numbers = gutter
 3403            .snapshot
 3404            .show_line_numbers
 3405            .unwrap_or_else(|| EditorSettings::get_global(cx).gutter.line_numbers);
 3406        if !include_line_numbers {
 3407            return Arc::default();
 3408        }
 3409
 3410        let relative = self.editor.read(cx).relative_line_numbers(cx);
 3411
 3412        let relative_line_numbers_enabled = relative.enabled();
 3413        let relative_rows = if relative_line_numbers_enabled
 3414            && let Some(current_selection_head) = current_selection_head
 3415        {
 3416            gutter.snapshot.calculate_relative_line_numbers(
 3417                &gutter.range,
 3418                current_selection_head,
 3419                relative.wrapped(),
 3420            )
 3421        } else {
 3422            Default::default()
 3423        };
 3424
 3425        let mut line_number = String::new();
 3426        let segments = gutter
 3427            .row_infos
 3428            .iter()
 3429            .enumerate()
 3430            .flat_map(|(ix, row_info)| {
 3431                let display_row = DisplayRow(gutter.range.start.0 + ix as u32);
 3432                line_number.clear();
 3433                let non_relative_number = if relative.wrapped() {
 3434                    row_info.buffer_row.or(row_info.wrapped_buffer_row)? + 1
 3435                } else {
 3436                    row_info.buffer_row? + 1
 3437                };
 3438                let relative_number = relative_rows.get(&display_row);
 3439                if !(relative_line_numbers_enabled && relative_number.is_some())
 3440                    && !gutter.snapshot.number_deleted_lines
 3441                    && row_info
 3442                        .diff_status
 3443                        .is_some_and(|status| status.is_deleted())
 3444                {
 3445                    return None;
 3446                }
 3447
 3448                let number = relative_number.unwrap_or(&non_relative_number);
 3449                write!(&mut line_number, "{number}").unwrap();
 3450
 3451                let color = active_rows
 3452                    .get(&display_row)
 3453                    .map(|spec| {
 3454                        if spec.breakpoint {
 3455                            cx.theme().colors().debugger_accent
 3456                        } else {
 3457                            cx.theme().colors().editor_active_line_number
 3458                        }
 3459                    })
 3460                    .unwrap_or_else(|| cx.theme().colors().editor_line_number);
 3461                let shaped_line =
 3462                    self.shape_line_number(SharedString::from(&line_number), color, window);
 3463                let scroll_top =
 3464                    gutter.scroll_position.y * ScrollPixelOffset::from(gutter.line_height);
 3465                let line_origin = gutter.hitbox.origin
 3466                    + point(
 3467                        gutter.hitbox.size.width
 3468                            - shaped_line.width
 3469                            - gutter.dimensions.right_padding,
 3470                        ix as f32 * gutter.line_height
 3471                            - Pixels::from(
 3472                                scroll_top % ScrollPixelOffset::from(gutter.line_height),
 3473                            ),
 3474                    );
 3475
 3476                #[cfg(not(test))]
 3477                let hitbox = Some(window.insert_hitbox(
 3478                    Bounds::new(line_origin, size(shaped_line.width, gutter.line_height)),
 3479                    HitboxBehavior::Normal,
 3480                ));
 3481                #[cfg(test)]
 3482                let hitbox = {
 3483                    let _ = line_origin;
 3484                    None
 3485                };
 3486
 3487                let segment = LineNumberSegment {
 3488                    shaped_line,
 3489                    hitbox,
 3490                };
 3491
 3492                let buffer_row = DisplayPoint::new(display_row, 0)
 3493                    .to_point(gutter.snapshot)
 3494                    .row;
 3495                let multi_buffer_row = MultiBufferRow(buffer_row);
 3496
 3497                Some((multi_buffer_row, segment))
 3498            });
 3499
 3500        let mut line_numbers: HashMap<MultiBufferRow, LineNumberLayout> = HashMap::default();
 3501        for (buffer_row, segment) in segments {
 3502            line_numbers
 3503                .entry(buffer_row)
 3504                .or_insert_with(|| LineNumberLayout {
 3505                    segments: Default::default(),
 3506                })
 3507                .segments
 3508                .push(segment);
 3509        }
 3510        Arc::new(line_numbers)
 3511    }
 3512
 3513    fn layout_crease_toggles(
 3514        &self,
 3515        rows: Range<DisplayRow>,
 3516        row_infos: &[RowInfo],
 3517        active_rows: &BTreeMap<DisplayRow, LineHighlightSpec>,
 3518        snapshot: &EditorSnapshot,
 3519        window: &mut Window,
 3520        cx: &mut App,
 3521    ) -> Vec<Option<AnyElement>> {
 3522        let include_fold_statuses = EditorSettings::get_global(cx).gutter.folds
 3523            && snapshot.mode.is_full()
 3524            && self.editor.read(cx).buffer_kind(cx) == ItemBufferKind::Singleton;
 3525        if include_fold_statuses {
 3526            row_infos
 3527                .iter()
 3528                .enumerate()
 3529                .map(|(ix, info)| {
 3530                    if info.expand_info.is_some() {
 3531                        return None;
 3532                    }
 3533                    let row = info.multibuffer_row?;
 3534                    let display_row = DisplayRow(rows.start.0 + ix as u32);
 3535                    let active = active_rows.contains_key(&display_row);
 3536
 3537                    snapshot.render_crease_toggle(row, active, self.editor.clone(), window, cx)
 3538                })
 3539                .collect()
 3540        } else {
 3541            Vec::new()
 3542        }
 3543    }
 3544
 3545    fn layout_crease_trailers(
 3546        &self,
 3547        buffer_rows: impl IntoIterator<Item = RowInfo>,
 3548        snapshot: &EditorSnapshot,
 3549        window: &mut Window,
 3550        cx: &mut App,
 3551    ) -> Vec<Option<AnyElement>> {
 3552        buffer_rows
 3553            .into_iter()
 3554            .map(|row_info| {
 3555                if row_info.expand_info.is_some() {
 3556                    return None;
 3557                }
 3558                if let Some(row) = row_info.multibuffer_row {
 3559                    snapshot.render_crease_trailer(row, window, cx)
 3560                } else {
 3561                    None
 3562                }
 3563            })
 3564            .collect()
 3565    }
 3566
 3567    fn bg_segments_per_row(
 3568        rows: Range<DisplayRow>,
 3569        selections: &[(PlayerColor, Vec<SelectionLayout>)],
 3570        highlight_ranges: &[(Range<DisplayPoint>, Hsla)],
 3571        base_background: Hsla,
 3572    ) -> Vec<Vec<(Range<DisplayPoint>, Hsla)>> {
 3573        if rows.start >= rows.end {
 3574            return Vec::new();
 3575        }
 3576        if !base_background.is_opaque() {
 3577            // We don't actually know what color is behind this editor.
 3578            return Vec::new();
 3579        }
 3580        let highlight_iter = highlight_ranges.iter().cloned();
 3581        let selection_iter = selections.iter().flat_map(|(player_color, layouts)| {
 3582            let color = player_color.selection;
 3583            layouts.iter().filter_map(move |selection_layout| {
 3584                if selection_layout.range.start != selection_layout.range.end {
 3585                    Some((selection_layout.range.clone(), color))
 3586                } else {
 3587                    None
 3588                }
 3589            })
 3590        });
 3591        let mut per_row_map = vec![Vec::new(); rows.len()];
 3592        for (range, color) in highlight_iter.chain(selection_iter) {
 3593            let covered_rows = if range.end.column() == 0 {
 3594                cmp::max(range.start.row(), rows.start)..cmp::min(range.end.row(), rows.end)
 3595            } else {
 3596                cmp::max(range.start.row(), rows.start)
 3597                    ..cmp::min(range.end.row().next_row(), rows.end)
 3598            };
 3599            for row in covered_rows.iter_rows() {
 3600                let seg_start = if row == range.start.row() {
 3601                    range.start
 3602                } else {
 3603                    DisplayPoint::new(row, 0)
 3604                };
 3605                let seg_end = if row == range.end.row() && range.end.column() != 0 {
 3606                    range.end
 3607                } else {
 3608                    DisplayPoint::new(row, u32::MAX)
 3609                };
 3610                let ix = row.minus(rows.start) as usize;
 3611                debug_assert!(row >= rows.start && row < rows.end);
 3612                debug_assert!(ix < per_row_map.len());
 3613                per_row_map[ix].push((seg_start..seg_end, color));
 3614            }
 3615        }
 3616        for row_segments in per_row_map.iter_mut() {
 3617            if row_segments.is_empty() {
 3618                continue;
 3619            }
 3620            let segments = mem::take(row_segments);
 3621            let merged = Self::merge_overlapping_ranges(segments, base_background);
 3622            *row_segments = merged;
 3623        }
 3624        per_row_map
 3625    }
 3626
 3627    /// Merge overlapping ranges by splitting at all range boundaries and blending colors where
 3628    /// multiple ranges overlap. The result contains non-overlapping ranges ordered from left to right.
 3629    ///
 3630    /// Expects `start.row() == end.row()` for each range.
 3631    fn merge_overlapping_ranges(
 3632        ranges: Vec<(Range<DisplayPoint>, Hsla)>,
 3633        base_background: Hsla,
 3634    ) -> Vec<(Range<DisplayPoint>, Hsla)> {
 3635        struct Boundary {
 3636            pos: DisplayPoint,
 3637            is_start: bool,
 3638            index: usize,
 3639            color: Hsla,
 3640        }
 3641
 3642        let mut boundaries: SmallVec<[Boundary; 16]> = SmallVec::with_capacity(ranges.len() * 2);
 3643        for (index, (range, color)) in ranges.iter().enumerate() {
 3644            debug_assert!(
 3645                range.start.row() == range.end.row(),
 3646                "expects single-row ranges"
 3647            );
 3648            if range.start < range.end {
 3649                boundaries.push(Boundary {
 3650                    pos: range.start,
 3651                    is_start: true,
 3652                    index,
 3653                    color: *color,
 3654                });
 3655                boundaries.push(Boundary {
 3656                    pos: range.end,
 3657                    is_start: false,
 3658                    index,
 3659                    color: *color,
 3660                });
 3661            }
 3662        }
 3663
 3664        if boundaries.is_empty() {
 3665            return Vec::new();
 3666        }
 3667
 3668        boundaries
 3669            .sort_unstable_by(|a, b| a.pos.cmp(&b.pos).then_with(|| a.is_start.cmp(&b.is_start)));
 3670
 3671        let mut processed_ranges: Vec<(Range<DisplayPoint>, Hsla)> = Vec::new();
 3672        let mut active_ranges: SmallVec<[(usize, Hsla); 8]> = SmallVec::new();
 3673
 3674        let mut i = 0;
 3675        let mut start_pos = boundaries[0].pos;
 3676
 3677        let boundaries_len = boundaries.len();
 3678        while i < boundaries_len {
 3679            let current_boundary_pos = boundaries[i].pos;
 3680            if start_pos < current_boundary_pos {
 3681                if !active_ranges.is_empty() {
 3682                    let mut color = base_background;
 3683                    for &(_, c) in &active_ranges {
 3684                        color = Hsla::blend(color, c);
 3685                    }
 3686                    if let Some((last_range, last_color)) = processed_ranges.last_mut() {
 3687                        if *last_color == color && last_range.end == start_pos {
 3688                            last_range.end = current_boundary_pos;
 3689                        } else {
 3690                            processed_ranges.push((start_pos..current_boundary_pos, color));
 3691                        }
 3692                    } else {
 3693                        processed_ranges.push((start_pos..current_boundary_pos, color));
 3694                    }
 3695                }
 3696            }
 3697            while i < boundaries_len && boundaries[i].pos == current_boundary_pos {
 3698                let active_range = &boundaries[i];
 3699                if active_range.is_start {
 3700                    let idx = active_range.index;
 3701                    let pos = active_ranges
 3702                        .binary_search_by_key(&idx, |(i, _)| *i)
 3703                        .unwrap_or_else(|p| p);
 3704                    active_ranges.insert(pos, (idx, active_range.color));
 3705                } else {
 3706                    let idx = active_range.index;
 3707                    if let Ok(pos) = active_ranges.binary_search_by_key(&idx, |(i, _)| *i) {
 3708                        active_ranges.remove(pos);
 3709                    }
 3710                }
 3711                i += 1;
 3712            }
 3713            start_pos = current_boundary_pos;
 3714        }
 3715
 3716        processed_ranges
 3717    }
 3718
 3719    fn layout_lines(
 3720        rows: Range<DisplayRow>,
 3721        snapshot: &EditorSnapshot,
 3722        style: &EditorStyle,
 3723        editor_width: Pixels,
 3724        is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
 3725        bg_segments_per_row: &[Vec<(Range<DisplayPoint>, Hsla)>],
 3726        window: &mut Window,
 3727        cx: &mut App,
 3728    ) -> Vec<LineWithInvisibles> {
 3729        if rows.start >= rows.end {
 3730            return Vec::new();
 3731        }
 3732
 3733        // Show the placeholder when the editor is empty
 3734        if snapshot.is_empty() {
 3735            let font_size = style.text.font_size.to_pixels(window.rem_size());
 3736            let placeholder_color = cx.theme().colors().text_placeholder;
 3737            let placeholder_text = snapshot.placeholder_text();
 3738
 3739            let placeholder_lines = placeholder_text
 3740                .as_ref()
 3741                .map_or(Vec::new(), |text| text.split('\n').collect::<Vec<_>>());
 3742
 3743            let placeholder_line_count = placeholder_lines.len();
 3744
 3745            placeholder_lines
 3746                .into_iter()
 3747                .skip(rows.start.0 as usize)
 3748                .chain(iter::repeat(""))
 3749                .take(cmp::max(rows.len(), placeholder_line_count))
 3750                .map(move |line| {
 3751                    let run = TextRun {
 3752                        len: line.len(),
 3753                        font: style.text.font(),
 3754                        color: placeholder_color,
 3755                        ..Default::default()
 3756                    };
 3757                    let line = window.text_system().shape_line(
 3758                        line.to_string().into(),
 3759                        font_size,
 3760                        &[run],
 3761                        None,
 3762                    );
 3763                    LineWithInvisibles {
 3764                        width: line.width,
 3765                        len: line.len,
 3766                        fragments: smallvec![LineFragment::Text(line)],
 3767                        invisibles: Vec::new(),
 3768                        font_size,
 3769                    }
 3770                })
 3771                .collect()
 3772        } else {
 3773            let use_tree_sitter = !snapshot.semantic_tokens_enabled
 3774                || snapshot.use_tree_sitter_for_syntax(rows.start, cx);
 3775            let language_aware = LanguageAwareStyling {
 3776                tree_sitter: use_tree_sitter,
 3777                diagnostics: true,
 3778            };
 3779            let chunks = snapshot.highlighted_chunks(rows.clone(), language_aware, style);
 3780            LineWithInvisibles::from_chunks(
 3781                chunks,
 3782                style,
 3783                MAX_LINE_LEN,
 3784                rows.len(),
 3785                &snapshot.mode,
 3786                editor_width,
 3787                is_row_soft_wrapped,
 3788                bg_segments_per_row,
 3789                window,
 3790                cx,
 3791            )
 3792        }
 3793    }
 3794
 3795    fn prepaint_lines(
 3796        &self,
 3797        start_row: DisplayRow,
 3798        line_layouts: &mut [LineWithInvisibles],
 3799        line_height: Pixels,
 3800        scroll_position: gpui::Point<ScrollOffset>,
 3801        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 3802        content_origin: gpui::Point<Pixels>,
 3803        window: &mut Window,
 3804        cx: &mut App,
 3805    ) -> SmallVec<[AnyElement; 1]> {
 3806        let mut line_elements = SmallVec::new();
 3807        for (ix, line) in line_layouts.iter_mut().enumerate() {
 3808            let row = start_row + DisplayRow(ix as u32);
 3809            line.prepaint(
 3810                line_height,
 3811                scroll_position,
 3812                scroll_pixel_position,
 3813                row,
 3814                content_origin,
 3815                &mut line_elements,
 3816                window,
 3817                cx,
 3818            );
 3819        }
 3820        line_elements
 3821    }
 3822
 3823    fn render_block(
 3824        &self,
 3825        block: &Block,
 3826        available_width: AvailableSpace,
 3827        block_id: BlockId,
 3828        block_row_start: DisplayRow,
 3829        snapshot: &EditorSnapshot,
 3830        text_x: Pixels,
 3831        rows: &Range<DisplayRow>,
 3832        line_layouts: &[LineWithInvisibles],
 3833        editor_margins: &EditorMargins,
 3834        line_height: Pixels,
 3835        em_width: Pixels,
 3836        text_hitbox: &Hitbox,
 3837        editor_width: Pixels,
 3838        scroll_width: &mut Pixels,
 3839        resized_blocks: &mut HashMap<CustomBlockId, u32>,
 3840        row_block_types: &mut HashMap<DisplayRow, bool>,
 3841        selections: &[Selection<Point>],
 3842        selected_buffer_ids: &Vec<BufferId>,
 3843        latest_selection_anchors: &HashMap<BufferId, Anchor>,
 3844        is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
 3845        sticky_header_excerpt_id: Option<BufferId>,
 3846        indent_guides: &Option<Vec<IndentGuideLayout>>,
 3847        block_resize_offset: &mut i32,
 3848        window: &mut Window,
 3849        cx: &mut App,
 3850    ) -> Option<(AnyElement, Size<Pixels>, DisplayRow, Pixels)> {
 3851        let mut x_position = None;
 3852        let mut element = match block {
 3853            Block::Custom(custom) => {
 3854                let block_start = custom.start().to_point(&snapshot.buffer_snapshot());
 3855                let block_end = custom.end().to_point(&snapshot.buffer_snapshot());
 3856                if block.place_near() && snapshot.is_line_folded(MultiBufferRow(block_start.row)) {
 3857                    return None;
 3858                }
 3859                let align_to = block_start.to_display_point(snapshot);
 3860                let x_and_width = |layout: &LineWithInvisibles| {
 3861                    Some((
 3862                        text_x + layout.x_for_index(align_to.column() as usize),
 3863                        text_x + layout.width,
 3864                    ))
 3865                };
 3866                let line_ix = align_to.row().0.checked_sub(rows.start.0);
 3867                x_position =
 3868                    if let Some(layout) = line_ix.and_then(|ix| line_layouts.get(ix as usize)) {
 3869                        x_and_width(layout)
 3870                    } else {
 3871                        x_and_width(&layout_line(
 3872                            align_to.row(),
 3873                            snapshot,
 3874                            &self.style,
 3875                            editor_width,
 3876                            is_row_soft_wrapped,
 3877                            window,
 3878                            cx,
 3879                        ))
 3880                    };
 3881
 3882                let anchor_x = x_position.unwrap().0;
 3883
 3884                let selected = selections
 3885                    .binary_search_by(|selection| {
 3886                        if selection.end <= block_start {
 3887                            Ordering::Less
 3888                        } else if selection.start >= block_end {
 3889                            Ordering::Greater
 3890                        } else {
 3891                            Ordering::Equal
 3892                        }
 3893                    })
 3894                    .is_ok();
 3895
 3896                div()
 3897                    .size_full()
 3898                    .child(
 3899                        custom.render(&mut BlockContext {
 3900                            window,
 3901                            app: cx,
 3902                            anchor_x,
 3903                            margins: editor_margins,
 3904                            line_height,
 3905                            em_width,
 3906                            block_id,
 3907                            height: custom.height.unwrap_or(1),
 3908                            selected,
 3909                            max_width: text_hitbox.size.width.max(*scroll_width),
 3910                            editor_style: &self.style,
 3911                            indent_guide_padding: indent_guides
 3912                                .as_ref()
 3913                                .map(|guides| {
 3914                                    Self::depth_zero_indent_guide_padding_for_row(
 3915                                        guides,
 3916                                        block_row_start,
 3917                                    )
 3918                                })
 3919                                .unwrap_or(px(0.0)),
 3920                        }),
 3921                    )
 3922                    .into_any()
 3923            }
 3924
 3925            Block::FoldedBuffer {
 3926                first_excerpt,
 3927                height,
 3928                ..
 3929            } => {
 3930                let mut result = v_flex().id(block_id).w_full().pr(editor_margins.right);
 3931
 3932                if self.should_show_buffer_headers() {
 3933                    let selected = selected_buffer_ids.contains(&first_excerpt.buffer_id());
 3934                    let jump_data = header_jump_data(
 3935                        snapshot,
 3936                        block_row_start,
 3937                        *height,
 3938                        first_excerpt,
 3939                        latest_selection_anchors,
 3940                    );
 3941                    result = result.child(self.render_buffer_header(
 3942                        first_excerpt,
 3943                        true,
 3944                        selected,
 3945                        false,
 3946                        jump_data,
 3947                        window,
 3948                        cx,
 3949                    ));
 3950                } else {
 3951                    result =
 3952                        result.child(div().h(FILE_HEADER_HEIGHT as f32 * window.line_height()));
 3953                }
 3954
 3955                result.into_any_element()
 3956            }
 3957
 3958            Block::ExcerptBoundary { .. } => {
 3959                let color = cx.theme().colors().clone();
 3960                let mut result = v_flex().id(block_id).w_full();
 3961
 3962                result = result.child(
 3963                    h_flex().relative().child(
 3964                        div()
 3965                            .top(line_height / 2.)
 3966                            .absolute()
 3967                            .w_full()
 3968                            .h_px()
 3969                            .bg(color.border_variant),
 3970                    ),
 3971                );
 3972
 3973                result.into_any()
 3974            }
 3975
 3976            Block::BufferHeader { excerpt, height } => {
 3977                let mut result = v_flex().id(block_id).w_full();
 3978
 3979                if self.should_show_buffer_headers() {
 3980                    let jump_data = header_jump_data(
 3981                        snapshot,
 3982                        block_row_start,
 3983                        *height,
 3984                        excerpt,
 3985                        latest_selection_anchors,
 3986                    );
 3987
 3988                    if sticky_header_excerpt_id != Some(excerpt.buffer_id()) {
 3989                        let selected = selected_buffer_ids.contains(&excerpt.buffer_id());
 3990
 3991                        result = result.child(div().pr(editor_margins.right).child(
 3992                            self.render_buffer_header(
 3993                                excerpt, false, selected, false, jump_data, window, cx,
 3994                            ),
 3995                        ));
 3996                    } else {
 3997                        result =
 3998                            result.child(div().h(FILE_HEADER_HEIGHT as f32 * window.line_height()));
 3999                    }
 4000                } else {
 4001                    result =
 4002                        result.child(div().h(FILE_HEADER_HEIGHT as f32 * window.line_height()));
 4003                }
 4004
 4005                result.into_any()
 4006            }
 4007
 4008            Block::Spacer { height, .. } => {
 4009                let indent_guide_padding = indent_guides
 4010                    .as_ref()
 4011                    .map(|guides| {
 4012                        Self::depth_zero_indent_guide_padding_for_row(guides, block_row_start)
 4013                    })
 4014                    .unwrap_or(px(0.0));
 4015                Self::render_spacer_block(
 4016                    block_id,
 4017                    *height,
 4018                    line_height,
 4019                    indent_guide_padding,
 4020                    window,
 4021                    cx,
 4022                )
 4023            }
 4024        };
 4025
 4026        // Discover the element's content height, then round up to the nearest multiple of line height.
 4027        let preliminary_size = element.layout_as_root(
 4028            size(available_width, AvailableSpace::MinContent),
 4029            window,
 4030            cx,
 4031        );
 4032        let quantized_height = (preliminary_size.height / line_height).ceil() * line_height;
 4033        let final_size = if preliminary_size.height == quantized_height {
 4034            preliminary_size
 4035        } else {
 4036            element.layout_as_root(size(available_width, quantized_height.into()), window, cx)
 4037        };
 4038        let mut element_height_in_lines = ((final_size.height / line_height).ceil() as u32).max(1);
 4039
 4040        let effective_row_start = block_row_start.0 as i32 + *block_resize_offset;
 4041        debug_assert!(effective_row_start >= 0);
 4042        let mut row = DisplayRow(effective_row_start.max(0) as u32);
 4043
 4044        let mut x_offset = px(0.);
 4045        let mut is_block = true;
 4046
 4047        if let BlockId::Custom(custom_block_id) = block_id
 4048            && block.has_height()
 4049        {
 4050            if block.place_near()
 4051                && let Some((x_target, line_width)) = x_position
 4052            {
 4053                let margin = em_width * 2;
 4054                if line_width + final_size.width + margin
 4055                    < editor_width + editor_margins.gutter.full_width()
 4056                    && !row_block_types.contains_key(&(row - 1))
 4057                    && element_height_in_lines == 1
 4058                {
 4059                    x_offset = line_width + margin;
 4060                    row = row - 1;
 4061                    is_block = false;
 4062                    element_height_in_lines = 0;
 4063                    row_block_types.insert(row, is_block);
 4064                } else {
 4065                    let max_offset =
 4066                        editor_width + editor_margins.gutter.full_width() - final_size.width;
 4067                    let min_offset = (x_target + em_width - final_size.width)
 4068                        .max(editor_margins.gutter.full_width());
 4069                    x_offset = x_target.min(max_offset).max(min_offset);
 4070                }
 4071            };
 4072            if element_height_in_lines != block.height() {
 4073                *block_resize_offset += element_height_in_lines as i32 - block.height() as i32;
 4074                resized_blocks.insert(custom_block_id, element_height_in_lines);
 4075            }
 4076        }
 4077        for i in 0..element_height_in_lines {
 4078            row_block_types.insert(row + i, is_block);
 4079        }
 4080
 4081        Some((element, final_size, row, x_offset))
 4082    }
 4083
 4084    /// The spacer pattern period must be an even factor of the line height, so
 4085    /// that two consecutive spacer blocks can render contiguously without an
 4086    /// obvious break in the pattern.
 4087    ///
 4088    /// Two consecutive spacers can appear when the other side has a diff hunk
 4089    /// and a custom block next to each other (e.g. merge conflict buttons).
 4090    fn spacer_pattern_period(line_height: f32, target_height: f32) -> f32 {
 4091        let k_approx = line_height / (2.0 * target_height);
 4092        let k_floor = (k_approx.floor() as u32).max(1);
 4093        let k_ceil = (k_approx.ceil() as u32).max(1);
 4094
 4095        let size_floor = line_height / (2 * k_floor) as f32;
 4096        let size_ceil = line_height / (2 * k_ceil) as f32;
 4097
 4098        if (size_floor - target_height).abs() <= (size_ceil - target_height).abs() {
 4099            size_floor
 4100        } else {
 4101            size_ceil
 4102        }
 4103    }
 4104
 4105    pub fn render_spacer_block(
 4106        block_id: BlockId,
 4107        block_height: u32,
 4108        line_height: Pixels,
 4109        indent_guide_padding: Pixels,
 4110        window: &mut Window,
 4111        cx: &App,
 4112    ) -> AnyElement {
 4113        let target_size = 16.0;
 4114        let scale = window.scale_factor();
 4115        let pattern_size =
 4116            Self::spacer_pattern_period(f32::from(line_height) * scale, target_size * scale);
 4117        let color = cx.theme().colors().panel_background;
 4118        let background = pattern_slash(color, 2.0, pattern_size - 2.0);
 4119
 4120        div()
 4121            .id(block_id)
 4122            .cursor(CursorStyle::Arrow)
 4123            .w_full()
 4124            .h((block_height as f32) * line_height)
 4125            .flex()
 4126            .flex_row()
 4127            .child(div().flex_shrink_0().w(indent_guide_padding).h_full())
 4128            .child(
 4129                div()
 4130                    .flex_1()
 4131                    .h_full()
 4132                    .relative()
 4133                    .overflow_x_hidden()
 4134                    .child(
 4135                        div()
 4136                            .absolute()
 4137                            .top_0()
 4138                            .bottom_0()
 4139                            .right_0()
 4140                            .left(-indent_guide_padding)
 4141                            .bg(background),
 4142                    ),
 4143            )
 4144            .into_any()
 4145    }
 4146
 4147    fn render_buffer_header(
 4148        &self,
 4149        for_excerpt: &ExcerptBoundaryInfo,
 4150        is_folded: bool,
 4151        is_selected: bool,
 4152        is_sticky: bool,
 4153        jump_data: JumpData,
 4154        window: &mut Window,
 4155        cx: &mut App,
 4156    ) -> impl IntoElement {
 4157        render_buffer_header(
 4158            &self.editor,
 4159            for_excerpt,
 4160            is_folded,
 4161            is_selected,
 4162            is_sticky,
 4163            jump_data,
 4164            window,
 4165            cx,
 4166        )
 4167    }
 4168
 4169    fn render_blocks(
 4170        &self,
 4171        rows: Range<DisplayRow>,
 4172        snapshot: &EditorSnapshot,
 4173        hitbox: &Hitbox,
 4174        text_hitbox: &Hitbox,
 4175        editor_width: Pixels,
 4176        scroll_width: &mut Pixels,
 4177        editor_margins: &EditorMargins,
 4178        em_width: Pixels,
 4179        text_x: Pixels,
 4180        line_height: Pixels,
 4181        line_layouts: &mut [LineWithInvisibles],
 4182        selections: &[Selection<Point>],
 4183        selected_buffer_ids: &Vec<BufferId>,
 4184        latest_selection_anchors: &HashMap<BufferId, Anchor>,
 4185        is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
 4186        sticky_header_excerpt_id: Option<BufferId>,
 4187        indent_guides: &Option<Vec<IndentGuideLayout>>,
 4188        window: &mut Window,
 4189        cx: &mut App,
 4190    ) -> RenderBlocksOutput {
 4191        let (fixed_blocks, non_fixed_blocks) = snapshot
 4192            .blocks_in_range(rows.clone())
 4193            .partition::<Vec<_>, _>(|(_, block)| block.style() == BlockStyle::Fixed);
 4194
 4195        let mut focused_block = self
 4196            .editor
 4197            .update(cx, |editor, _| editor.take_focused_block());
 4198        let mut fixed_block_max_width = Pixels::ZERO;
 4199        let mut blocks = Vec::new();
 4200        let mut spacer_blocks = Vec::new();
 4201        let mut resized_blocks = HashMap::default();
 4202        let mut row_block_types = HashMap::default();
 4203        let mut block_resize_offset: i32 = 0;
 4204
 4205        for (row, block) in fixed_blocks {
 4206            let block_id = block.id();
 4207
 4208            if focused_block.as_ref().is_some_and(|b| b.id == block_id) {
 4209                focused_block = None;
 4210            }
 4211
 4212            if let Some((element, element_size, row, x_offset)) = self.render_block(
 4213                block,
 4214                AvailableSpace::MinContent,
 4215                block_id,
 4216                row,
 4217                snapshot,
 4218                text_x,
 4219                &rows,
 4220                line_layouts,
 4221                editor_margins,
 4222                line_height,
 4223                em_width,
 4224                text_hitbox,
 4225                editor_width,
 4226                scroll_width,
 4227                &mut resized_blocks,
 4228                &mut row_block_types,
 4229                selections,
 4230                selected_buffer_ids,
 4231                latest_selection_anchors,
 4232                is_row_soft_wrapped,
 4233                sticky_header_excerpt_id,
 4234                indent_guides,
 4235                &mut block_resize_offset,
 4236                window,
 4237                cx,
 4238            ) {
 4239                fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
 4240                blocks.push(BlockLayout {
 4241                    id: block_id,
 4242                    x_offset,
 4243                    row: Some(row),
 4244                    element,
 4245                    available_space: size(AvailableSpace::MinContent, element_size.height.into()),
 4246                    style: BlockStyle::Fixed,
 4247                    overlaps_gutter: true,
 4248                    is_buffer_header: block.is_buffer_header(),
 4249                });
 4250            }
 4251        }
 4252
 4253        for (row, block) in non_fixed_blocks {
 4254            let style = block.style();
 4255            let width = match (style, block.place_near()) {
 4256                (_, true) => AvailableSpace::MinContent,
 4257                (BlockStyle::Sticky, _) => hitbox.size.width.into(),
 4258                (BlockStyle::Flex, _) => hitbox
 4259                    .size
 4260                    .width
 4261                    .max(fixed_block_max_width)
 4262                    .max(
 4263                        editor_margins.gutter.width + *scroll_width + editor_margins.extended_right,
 4264                    )
 4265                    .into(),
 4266                (BlockStyle::Spacer, _) => hitbox
 4267                    .size
 4268                    .width
 4269                    .max(fixed_block_max_width)
 4270                    .max(*scroll_width + editor_margins.extended_right)
 4271                    .into(),
 4272                (BlockStyle::Fixed, _) => unreachable!(),
 4273            };
 4274            let block_id = block.id();
 4275
 4276            if focused_block.as_ref().is_some_and(|b| b.id == block_id) {
 4277                focused_block = None;
 4278            }
 4279
 4280            if let Some((element, element_size, row, x_offset)) = self.render_block(
 4281                block,
 4282                width,
 4283                block_id,
 4284                row,
 4285                snapshot,
 4286                text_x,
 4287                &rows,
 4288                line_layouts,
 4289                editor_margins,
 4290                line_height,
 4291                em_width,
 4292                text_hitbox,
 4293                editor_width,
 4294                scroll_width,
 4295                &mut resized_blocks,
 4296                &mut row_block_types,
 4297                selections,
 4298                selected_buffer_ids,
 4299                latest_selection_anchors,
 4300                is_row_soft_wrapped,
 4301                sticky_header_excerpt_id,
 4302                indent_guides,
 4303                &mut block_resize_offset,
 4304                window,
 4305                cx,
 4306            ) {
 4307                let layout = BlockLayout {
 4308                    id: block_id,
 4309                    x_offset,
 4310                    row: Some(row),
 4311                    element,
 4312                    available_space: size(width, element_size.height.into()),
 4313                    style,
 4314                    overlaps_gutter: !block.place_near() && style != BlockStyle::Spacer,
 4315                    is_buffer_header: block.is_buffer_header(),
 4316                };
 4317                if style == BlockStyle::Spacer {
 4318                    spacer_blocks.push(layout);
 4319                } else {
 4320                    blocks.push(layout);
 4321                }
 4322            }
 4323        }
 4324
 4325        if let Some(focused_block) = focused_block
 4326            && let Some(focus_handle) = focused_block.focus_handle.upgrade()
 4327            && focus_handle.is_focused(window)
 4328            && let Some(block) = snapshot.block_for_id(focused_block.id)
 4329        {
 4330            let style = block.style();
 4331            let width = match style {
 4332                BlockStyle::Fixed => AvailableSpace::MinContent,
 4333                BlockStyle::Flex => {
 4334                    AvailableSpace::Definite(hitbox.size.width.max(fixed_block_max_width).max(
 4335                        editor_margins.gutter.width + *scroll_width + editor_margins.extended_right,
 4336                    ))
 4337                }
 4338                BlockStyle::Spacer => AvailableSpace::Definite(
 4339                    hitbox
 4340                        .size
 4341                        .width
 4342                        .max(fixed_block_max_width)
 4343                        .max(*scroll_width + editor_margins.extended_right),
 4344                ),
 4345                BlockStyle::Sticky => AvailableSpace::Definite(hitbox.size.width),
 4346            };
 4347
 4348            if let Some((element, element_size, _, x_offset)) = self.render_block(
 4349                &block,
 4350                width,
 4351                focused_block.id,
 4352                rows.end,
 4353                snapshot,
 4354                text_x,
 4355                &rows,
 4356                line_layouts,
 4357                editor_margins,
 4358                line_height,
 4359                em_width,
 4360                text_hitbox,
 4361                editor_width,
 4362                scroll_width,
 4363                &mut resized_blocks,
 4364                &mut row_block_types,
 4365                selections,
 4366                selected_buffer_ids,
 4367                latest_selection_anchors,
 4368                is_row_soft_wrapped,
 4369                sticky_header_excerpt_id,
 4370                indent_guides,
 4371                &mut block_resize_offset,
 4372                window,
 4373                cx,
 4374            ) {
 4375                blocks.push(BlockLayout {
 4376                    id: block.id(),
 4377                    x_offset,
 4378                    row: None,
 4379                    element,
 4380                    available_space: size(width, element_size.height.into()),
 4381                    style,
 4382                    overlaps_gutter: true,
 4383                    is_buffer_header: block.is_buffer_header(),
 4384                });
 4385            }
 4386        }
 4387
 4388        if resized_blocks.is_empty() {
 4389            *scroll_width =
 4390                (*scroll_width).max(fixed_block_max_width - editor_margins.gutter.width);
 4391        }
 4392
 4393        RenderBlocksOutput {
 4394            non_spacer_blocks: blocks,
 4395            spacer_blocks,
 4396            row_block_types,
 4397            resized_blocks: (!resized_blocks.is_empty()).then_some(resized_blocks),
 4398        }
 4399    }
 4400
 4401    fn layout_blocks(
 4402        &self,
 4403        blocks: &mut Vec<BlockLayout>,
 4404        hitbox: &Hitbox,
 4405        gutter_hitbox: &Hitbox,
 4406        line_height: Pixels,
 4407        scroll_position: gpui::Point<ScrollOffset>,
 4408        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 4409        editor_margins: &EditorMargins,
 4410        window: &mut Window,
 4411        cx: &mut App,
 4412    ) {
 4413        for block in blocks {
 4414            let mut origin = if let Some(row) = block.row {
 4415                hitbox.origin
 4416                    + point(
 4417                        block.x_offset,
 4418                        Pixels::from(
 4419                            (row.as_f64() - scroll_position.y)
 4420                                * ScrollPixelOffset::from(line_height),
 4421                        ),
 4422                    )
 4423            } else {
 4424                // Position the block outside the visible area
 4425                hitbox.origin + point(Pixels::ZERO, hitbox.size.height)
 4426            };
 4427
 4428            if block.style == BlockStyle::Spacer {
 4429                origin += point(
 4430                    gutter_hitbox.size.width + editor_margins.gutter.margin,
 4431                    Pixels::ZERO,
 4432                );
 4433            }
 4434
 4435            if !matches!(block.style, BlockStyle::Sticky) {
 4436                origin += point(Pixels::from(-scroll_pixel_position.x), Pixels::ZERO);
 4437            }
 4438
 4439            let focus_handle =
 4440                block
 4441                    .element
 4442                    .prepaint_as_root(origin, block.available_space, window, cx);
 4443
 4444            if let Some(focus_handle) = focus_handle {
 4445                self.editor.update(cx, |editor, _cx| {
 4446                    editor.set_focused_block(FocusedBlock {
 4447                        id: block.id,
 4448                        focus_handle: focus_handle.downgrade(),
 4449                    });
 4450                });
 4451            }
 4452        }
 4453    }
 4454
 4455    fn layout_sticky_buffer_header(
 4456        &self,
 4457        StickyHeaderExcerpt { excerpt }: StickyHeaderExcerpt<'_>,
 4458        scroll_position: gpui::Point<ScrollOffset>,
 4459        line_height: Pixels,
 4460        right_margin: Pixels,
 4461        snapshot: &EditorSnapshot,
 4462        hitbox: &Hitbox,
 4463        selected_buffer_ids: &Vec<BufferId>,
 4464        blocks: &[BlockLayout],
 4465        latest_selection_anchors: &HashMap<BufferId, Anchor>,
 4466        window: &mut Window,
 4467        cx: &mut App,
 4468    ) -> AnyElement {
 4469        let jump_data = header_jump_data(
 4470            snapshot,
 4471            DisplayRow(scroll_position.y as u32),
 4472            FILE_HEADER_HEIGHT + MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
 4473            excerpt,
 4474            latest_selection_anchors,
 4475        );
 4476
 4477        let editor_bg_color = cx.theme().colors().editor_background;
 4478
 4479        let selected = selected_buffer_ids.contains(&excerpt.buffer_id());
 4480
 4481        let available_width = hitbox.bounds.size.width - right_margin;
 4482
 4483        let mut header = v_flex()
 4484            .w_full()
 4485            .relative()
 4486            .child(
 4487                div()
 4488                    .w(available_width)
 4489                    .h(FILE_HEADER_HEIGHT as f32 * line_height)
 4490                    .bg(linear_gradient(
 4491                        0.,
 4492                        linear_color_stop(editor_bg_color.opacity(0.), 0.),
 4493                        linear_color_stop(editor_bg_color, 0.6),
 4494                    ))
 4495                    .absolute()
 4496                    .top_0(),
 4497            )
 4498            .child(
 4499                self.render_buffer_header(excerpt, false, selected, true, jump_data, window, cx)
 4500                    .into_any_element(),
 4501            )
 4502            .into_any_element();
 4503
 4504        let mut origin = hitbox.origin;
 4505        // Move floating header up to avoid colliding with the next buffer header.
 4506        for block in blocks.iter() {
 4507            if !block.is_buffer_header {
 4508                continue;
 4509            }
 4510
 4511            let Some(display_row) = block.row.filter(|row| row.0 > scroll_position.y as u32) else {
 4512                continue;
 4513            };
 4514
 4515            let max_row = display_row.0.saturating_sub(FILE_HEADER_HEIGHT);
 4516            let offset = scroll_position.y - max_row as f64;
 4517
 4518            if offset > 0.0 {
 4519                origin.y -= Pixels::from(offset * ScrollPixelOffset::from(line_height));
 4520            }
 4521            break;
 4522        }
 4523
 4524        let size = size(
 4525            AvailableSpace::Definite(available_width),
 4526            AvailableSpace::MinContent,
 4527        );
 4528
 4529        header.prepaint_as_root(origin, size, window, cx);
 4530
 4531        header
 4532    }
 4533
 4534    fn layout_sticky_headers(
 4535        &self,
 4536        snapshot: &EditorSnapshot,
 4537        editor_width: Pixels,
 4538        is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
 4539        line_height: Pixels,
 4540        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 4541        content_origin: gpui::Point<Pixels>,
 4542        gutter_dimensions: &GutterDimensions,
 4543        gutter_hitbox: &Hitbox,
 4544        text_hitbox: &Hitbox,
 4545        relative_line_numbers: RelativeLineNumbers,
 4546        relative_to: Option<DisplayRow>,
 4547        window: &mut Window,
 4548        cx: &mut App,
 4549    ) -> Option<StickyHeaders> {
 4550        let show_line_numbers = snapshot
 4551            .show_line_numbers
 4552            .unwrap_or_else(|| EditorSettings::get_global(cx).gutter.line_numbers);
 4553
 4554        let rows = Self::sticky_headers(self.editor.read(cx), snapshot);
 4555
 4556        let mut lines = Vec::<StickyHeaderLine>::new();
 4557
 4558        for StickyHeader {
 4559            sticky_row,
 4560            start_point,
 4561            offset,
 4562        } in rows.into_iter().rev()
 4563        {
 4564            let line = layout_line(
 4565                sticky_row,
 4566                snapshot,
 4567                &self.style,
 4568                editor_width,
 4569                is_row_soft_wrapped,
 4570                window,
 4571                cx,
 4572            );
 4573
 4574            let line_number = show_line_numbers.then(|| {
 4575                let start_display_row = start_point.to_display_point(snapshot).row();
 4576                let relative_number = relative_to
 4577                    .filter(|_| relative_line_numbers != RelativeLineNumbers::Disabled)
 4578                    .map(|base| {
 4579                        snapshot.relative_line_delta(
 4580                            base,
 4581                            start_display_row,
 4582                            relative_line_numbers == RelativeLineNumbers::Wrapped,
 4583                        )
 4584                    });
 4585                let number = relative_number
 4586                    .filter(|&delta| delta != 0)
 4587                    .map(|delta| delta.unsigned_abs() as u32)
 4588                    .unwrap_or(start_point.row + 1);
 4589                let color = cx.theme().colors().editor_line_number;
 4590                self.shape_line_number(SharedString::from(number.to_string()), color, window)
 4591            });
 4592
 4593            lines.push(StickyHeaderLine::new(
 4594                sticky_row,
 4595                line_height * offset as f32,
 4596                line,
 4597                line_number,
 4598                line_height,
 4599                scroll_pixel_position,
 4600                content_origin,
 4601                gutter_hitbox,
 4602                text_hitbox,
 4603                window,
 4604                cx,
 4605            ));
 4606        }
 4607
 4608        lines.reverse();
 4609        if lines.is_empty() {
 4610            return None;
 4611        }
 4612
 4613        Some(StickyHeaders {
 4614            lines,
 4615            gutter_background: cx.theme().colors().editor_gutter_background,
 4616            content_background: self.style.background,
 4617            gutter_right_padding: gutter_dimensions.right_padding,
 4618        })
 4619    }
 4620
 4621    pub(crate) fn sticky_headers(editor: &Editor, snapshot: &EditorSnapshot) -> Vec<StickyHeader> {
 4622        let scroll_top = snapshot.scroll_position().y;
 4623
 4624        let mut end_rows = Vec::<DisplayRow>::new();
 4625        let mut rows = Vec::<StickyHeader>::new();
 4626
 4627        for item in editor.sticky_headers.iter().flatten() {
 4628            let start_point = item.range.start.to_point(snapshot.buffer_snapshot());
 4629            let end_point = item.range.end.to_point(snapshot.buffer_snapshot());
 4630
 4631            let sticky_row = snapshot
 4632                .display_snapshot
 4633                .point_to_display_point(start_point, Bias::Left)
 4634                .row();
 4635            if rows
 4636                .last()
 4637                .is_some_and(|last| last.sticky_row == sticky_row)
 4638            {
 4639                continue;
 4640            }
 4641
 4642            let end_row = snapshot
 4643                .display_snapshot
 4644                .point_to_display_point(end_point, Bias::Left)
 4645                .row();
 4646            let max_sticky_row = end_row.previous_row();
 4647            if max_sticky_row <= sticky_row {
 4648                continue;
 4649            }
 4650
 4651            while end_rows
 4652                .last()
 4653                .is_some_and(|&last_end| last_end <= sticky_row)
 4654            {
 4655                end_rows.pop();
 4656            }
 4657            let depth = end_rows.len();
 4658            let adjusted_scroll_top = scroll_top + depth as f64;
 4659
 4660            if sticky_row.as_f64() >= adjusted_scroll_top || end_row.as_f64() <= adjusted_scroll_top
 4661            {
 4662                continue;
 4663            }
 4664
 4665            let max_scroll_offset = max_sticky_row.as_f64() - scroll_top;
 4666            let offset = (depth as f64).min(max_scroll_offset);
 4667
 4668            end_rows.push(end_row);
 4669            rows.push(StickyHeader {
 4670                sticky_row,
 4671                start_point,
 4672                offset,
 4673            });
 4674        }
 4675
 4676        rows
 4677    }
 4678
 4679    fn layout_cursor_popovers(
 4680        &self,
 4681        line_height: Pixels,
 4682        text_hitbox: &Hitbox,
 4683        content_origin: gpui::Point<Pixels>,
 4684        right_margin: Pixels,
 4685        start_row: DisplayRow,
 4686        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 4687        line_layouts: &[LineWithInvisibles],
 4688        cursor: DisplayPoint,
 4689        cursor_point: Point,
 4690        style: &EditorStyle,
 4691        window: &mut Window,
 4692        cx: &mut App,
 4693    ) -> Option<ContextMenuLayout> {
 4694        let mut min_menu_height = Pixels::ZERO;
 4695        let mut max_menu_height = Pixels::ZERO;
 4696        let mut height_above_menu = Pixels::ZERO;
 4697        let height_below_menu = Pixels::ZERO;
 4698        let mut edit_prediction_popover_visible = false;
 4699        let mut context_menu_visible = false;
 4700        let context_menu_placement;
 4701
 4702        {
 4703            let editor = self.editor.read(cx);
 4704            if editor.edit_prediction_visible_in_cursor_popover(editor.has_active_edit_prediction())
 4705            {
 4706                height_above_menu +=
 4707                    editor.edit_prediction_cursor_popover_height() + POPOVER_Y_PADDING;
 4708                edit_prediction_popover_visible = true;
 4709            }
 4710
 4711            if editor.context_menu_visible()
 4712                && let Some(crate::ContextMenuOrigin::Cursor) = editor.context_menu_origin()
 4713            {
 4714                let (min_height_in_lines, max_height_in_lines) = editor
 4715                    .context_menu_options
 4716                    .as_ref()
 4717                    .map_or((3, 12), |options| {
 4718                        (options.min_entries_visible, options.max_entries_visible)
 4719                    });
 4720
 4721                min_menu_height += line_height * min_height_in_lines as f32 + POPOVER_Y_PADDING;
 4722                max_menu_height += line_height * max_height_in_lines as f32 + POPOVER_Y_PADDING;
 4723                context_menu_visible = true;
 4724            }
 4725            context_menu_placement = editor
 4726                .context_menu_options
 4727                .as_ref()
 4728                .and_then(|options| options.placement.clone());
 4729        }
 4730
 4731        let visible = edit_prediction_popover_visible || context_menu_visible;
 4732        if !visible {
 4733            return None;
 4734        }
 4735
 4736        let cursor_row_layout = &line_layouts[cursor.row().minus(start_row) as usize];
 4737        let target_position = content_origin
 4738            + gpui::Point {
 4739                x: cmp::max(
 4740                    px(0.),
 4741                    Pixels::from(
 4742                        ScrollPixelOffset::from(
 4743                            cursor_row_layout.x_for_index(cursor.column() as usize),
 4744                        ) - scroll_pixel_position.x,
 4745                    ),
 4746                ),
 4747                y: cmp::max(
 4748                    px(0.),
 4749                    Pixels::from(
 4750                        cursor.row().next_row().as_f64() * ScrollPixelOffset::from(line_height)
 4751                            - scroll_pixel_position.y,
 4752                    ),
 4753                ),
 4754            };
 4755
 4756        let viewport_bounds =
 4757            Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
 4758                right: -right_margin - MENU_GAP,
 4759                ..Default::default()
 4760            });
 4761
 4762        let min_height = height_above_menu + min_menu_height + height_below_menu;
 4763        let max_height = height_above_menu + max_menu_height + height_below_menu;
 4764        let (laid_out_popovers, y_flipped) = self.layout_popovers_above_or_below_line(
 4765            target_position,
 4766            line_height,
 4767            min_height,
 4768            max_height,
 4769            context_menu_placement,
 4770            text_hitbox,
 4771            viewport_bounds,
 4772            window,
 4773            cx,
 4774            |height, max_width_for_stable_x, y_flipped, window, cx| {
 4775                // First layout the menu to get its size - others can be at least this wide.
 4776                let context_menu = if context_menu_visible {
 4777                    let menu_height = if y_flipped {
 4778                        height - height_below_menu
 4779                    } else {
 4780                        height - height_above_menu
 4781                    };
 4782                    let mut element = self
 4783                        .render_context_menu(line_height, menu_height, window, cx)
 4784                        .expect("Visible context menu should always render.");
 4785                    let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
 4786                    Some((CursorPopoverType::CodeContextMenu, element, size))
 4787                } else {
 4788                    None
 4789                };
 4790                let min_width = context_menu
 4791                    .as_ref()
 4792                    .map_or(px(0.), |(_, _, size)| size.width);
 4793                let max_width = max_width_for_stable_x.max(
 4794                    context_menu
 4795                        .as_ref()
 4796                        .map_or(px(0.), |(_, _, size)| size.width),
 4797                );
 4798
 4799                let edit_prediction = if edit_prediction_popover_visible {
 4800                    self.editor.update(cx, move |editor, cx| {
 4801                        let mut element = editor.render_edit_prediction_cursor_popover(
 4802                            min_width,
 4803                            max_width,
 4804                            cursor_point,
 4805                            style,
 4806                            window,
 4807                            cx,
 4808                        )?;
 4809                        let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
 4810                        Some((CursorPopoverType::EditPrediction, element, size))
 4811                    })
 4812                } else {
 4813                    None
 4814                };
 4815                vec![edit_prediction, context_menu]
 4816                    .into_iter()
 4817                    .flatten()
 4818                    .collect::<Vec<_>>()
 4819            },
 4820        )?;
 4821
 4822        let (menu_ix, (_, menu_bounds)) = laid_out_popovers
 4823            .iter()
 4824            .find_position(|(x, _)| matches!(x, CursorPopoverType::CodeContextMenu))?;
 4825        let last_ix = laid_out_popovers.len() - 1;
 4826        let menu_is_last = menu_ix == last_ix;
 4827        let first_popover_bounds = laid_out_popovers[0].1;
 4828        let last_popover_bounds = laid_out_popovers[last_ix].1;
 4829
 4830        // Bounds to layout the aside around. When y_flipped, the aside goes either above or to the
 4831        // right, and otherwise it goes below or to the right.
 4832        let mut target_bounds = Bounds::from_corners(
 4833            first_popover_bounds.origin,
 4834            last_popover_bounds.bottom_right(),
 4835        );
 4836        target_bounds.size.width = menu_bounds.size.width;
 4837
 4838        // Like `target_bounds`, but with the max height it could occupy. Choosing an aside position
 4839        // based on this is preferred for layout stability.
 4840        let mut max_target_bounds = target_bounds;
 4841        max_target_bounds.size.height = max_height;
 4842        if y_flipped {
 4843            max_target_bounds.origin.y -= max_height - target_bounds.size.height;
 4844        }
 4845
 4846        // Add spacing around `target_bounds` and `max_target_bounds`.
 4847        let mut extend_amount = Edges::all(MENU_GAP);
 4848        if y_flipped {
 4849            extend_amount.bottom = line_height;
 4850        } else {
 4851            extend_amount.top = line_height;
 4852        }
 4853        let target_bounds = target_bounds.extend(extend_amount);
 4854        let max_target_bounds = max_target_bounds.extend(extend_amount);
 4855
 4856        let must_place_above_or_below =
 4857            if y_flipped && !menu_is_last && menu_bounds.size.height < max_menu_height {
 4858                laid_out_popovers[menu_ix + 1..]
 4859                    .iter()
 4860                    .any(|(_, popover_bounds)| popover_bounds.size.width > menu_bounds.size.width)
 4861            } else {
 4862                false
 4863            };
 4864
 4865        let aside_bounds = self.layout_context_menu_aside(
 4866            y_flipped,
 4867            *menu_bounds,
 4868            target_bounds,
 4869            max_target_bounds,
 4870            max_menu_height,
 4871            must_place_above_or_below,
 4872            text_hitbox,
 4873            viewport_bounds,
 4874            window,
 4875            cx,
 4876        );
 4877
 4878        if let Some(menu_bounds) = laid_out_popovers.iter().find_map(|(popover_type, bounds)| {
 4879            if matches!(popover_type, CursorPopoverType::CodeContextMenu) {
 4880                Some(*bounds)
 4881            } else {
 4882                None
 4883            }
 4884        }) {
 4885            let bounds = if let Some(aside_bounds) = aside_bounds {
 4886                menu_bounds.union(&aside_bounds)
 4887            } else {
 4888                menu_bounds
 4889            };
 4890            return Some(ContextMenuLayout { y_flipped, bounds });
 4891        }
 4892
 4893        None
 4894    }
 4895
 4896    fn layout_gutter_menu(
 4897        &self,
 4898        line_height: Pixels,
 4899        text_hitbox: &Hitbox,
 4900        content_origin: gpui::Point<Pixels>,
 4901        right_margin: Pixels,
 4902        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 4903        gutter_overshoot: Pixels,
 4904        window: &mut Window,
 4905        cx: &mut App,
 4906    ) {
 4907        let editor = self.editor.read(cx);
 4908        if !editor.context_menu_visible() {
 4909            return;
 4910        }
 4911        let Some(crate::ContextMenuOrigin::GutterIndicator(gutter_row)) =
 4912            editor.context_menu_origin()
 4913        else {
 4914            return;
 4915        };
 4916        // Context menu was spawned via a click on a gutter. Ensure it's a bit closer to the
 4917        // indicator than just a plain first column of the text field.
 4918        let target_position = content_origin
 4919            + gpui::Point {
 4920                x: -gutter_overshoot,
 4921                y: Pixels::from(
 4922                    gutter_row.next_row().as_f64() * ScrollPixelOffset::from(line_height)
 4923                        - scroll_pixel_position.y,
 4924                ),
 4925            };
 4926
 4927        let (min_height_in_lines, max_height_in_lines) = editor
 4928            .context_menu_options
 4929            .as_ref()
 4930            .map_or((3, 12), |options| {
 4931                (options.min_entries_visible, options.max_entries_visible)
 4932            });
 4933
 4934        let min_height = line_height * min_height_in_lines as f32 + POPOVER_Y_PADDING;
 4935        let max_height = line_height * max_height_in_lines as f32 + POPOVER_Y_PADDING;
 4936        let viewport_bounds =
 4937            Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
 4938                right: -right_margin - MENU_GAP,
 4939                ..Default::default()
 4940            });
 4941        self.layout_popovers_above_or_below_line(
 4942            target_position,
 4943            line_height,
 4944            min_height,
 4945            max_height,
 4946            editor
 4947                .context_menu_options
 4948                .as_ref()
 4949                .and_then(|options| options.placement.clone()),
 4950            text_hitbox,
 4951            viewport_bounds,
 4952            window,
 4953            cx,
 4954            move |height, _max_width_for_stable_x, _, window, cx| {
 4955                let mut element = self
 4956                    .render_context_menu(line_height, height, window, cx)
 4957                    .expect("Visible context menu should always render.");
 4958                let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
 4959                vec![(CursorPopoverType::CodeContextMenu, element, size)]
 4960            },
 4961        );
 4962    }
 4963
 4964    fn layout_popovers_above_or_below_line(
 4965        &self,
 4966        target_position: gpui::Point<Pixels>,
 4967        line_height: Pixels,
 4968        min_height: Pixels,
 4969        max_height: Pixels,
 4970        placement: Option<ContextMenuPlacement>,
 4971        text_hitbox: &Hitbox,
 4972        viewport_bounds: Bounds<Pixels>,
 4973        window: &mut Window,
 4974        cx: &mut App,
 4975        make_sized_popovers: impl FnOnce(
 4976            Pixels,
 4977            Pixels,
 4978            bool,
 4979            &mut Window,
 4980            &mut App,
 4981        ) -> Vec<(CursorPopoverType, AnyElement, Size<Pixels>)>,
 4982    ) -> Option<(Vec<(CursorPopoverType, Bounds<Pixels>)>, bool)> {
 4983        let text_style = TextStyleRefinement {
 4984            line_height: Some(DefiniteLength::Fraction(
 4985                BufferLineHeight::Comfortable.value(),
 4986            )),
 4987            ..Default::default()
 4988        };
 4989        window.with_text_style(Some(text_style), |window| {
 4990            // If the max height won't fit below and there is more space above, put it above the line.
 4991            let bottom_y_when_flipped = target_position.y - line_height;
 4992            let available_above = bottom_y_when_flipped - text_hitbox.top();
 4993            let available_below = text_hitbox.bottom() - target_position.y;
 4994            let y_overflows_below = max_height > available_below;
 4995            let mut y_flipped = match placement {
 4996                Some(ContextMenuPlacement::Above) => true,
 4997                Some(ContextMenuPlacement::Below) => false,
 4998                None => y_overflows_below && available_above > available_below,
 4999            };
 5000            let mut height = cmp::min(
 5001                max_height,
 5002                if y_flipped {
 5003                    available_above
 5004                } else {
 5005                    available_below
 5006                },
 5007            );
 5008
 5009            // If the min height doesn't fit within text bounds, instead fit within the window.
 5010            if height < min_height {
 5011                let available_above = bottom_y_when_flipped;
 5012                let available_below = viewport_bounds.bottom() - target_position.y;
 5013                let (y_flipped_override, height_override) = match placement {
 5014                    Some(ContextMenuPlacement::Above) => {
 5015                        (true, cmp::min(available_above, min_height))
 5016                    }
 5017                    Some(ContextMenuPlacement::Below) => {
 5018                        (false, cmp::min(available_below, min_height))
 5019                    }
 5020                    None => {
 5021                        if available_below > min_height {
 5022                            (false, min_height)
 5023                        } else if available_above > min_height {
 5024                            (true, min_height)
 5025                        } else if available_above > available_below {
 5026                            (true, available_above)
 5027                        } else {
 5028                            (false, available_below)
 5029                        }
 5030                    }
 5031                };
 5032                y_flipped = y_flipped_override;
 5033                height = height_override;
 5034            }
 5035
 5036            let max_width_for_stable_x = viewport_bounds.right() - target_position.x;
 5037
 5038            // TODO: Use viewport_bounds.width as a max width so that it doesn't get clipped on the left
 5039            // for very narrow windows.
 5040            let popovers =
 5041                make_sized_popovers(height, max_width_for_stable_x, y_flipped, window, cx);
 5042            if popovers.is_empty() {
 5043                return None;
 5044            }
 5045
 5046            let max_width = popovers
 5047                .iter()
 5048                .map(|(_, _, size)| size.width)
 5049                .max()
 5050                .unwrap_or_default();
 5051
 5052            let mut current_position = gpui::Point {
 5053                // Snap the right edge of the list to the right edge of the window if its horizontal bounds
 5054                // overflow. Include space for the scrollbar.
 5055                x: target_position
 5056                    .x
 5057                    .min((viewport_bounds.right() - max_width).max(Pixels::ZERO)),
 5058                y: if y_flipped {
 5059                    bottom_y_when_flipped
 5060                } else {
 5061                    target_position.y
 5062                },
 5063            };
 5064
 5065            let mut laid_out_popovers = popovers
 5066                .into_iter()
 5067                .map(|(popover_type, element, size)| {
 5068                    if y_flipped {
 5069                        current_position.y -= size.height;
 5070                    }
 5071                    let position = current_position;
 5072                    window.defer_draw(element, current_position, 1, None);
 5073                    if !y_flipped {
 5074                        current_position.y += size.height + MENU_GAP;
 5075                    } else {
 5076                        current_position.y -= MENU_GAP;
 5077                    }
 5078                    (popover_type, Bounds::new(position, size))
 5079                })
 5080                .collect::<Vec<_>>();
 5081
 5082            if y_flipped {
 5083                laid_out_popovers.reverse();
 5084            }
 5085
 5086            Some((laid_out_popovers, y_flipped))
 5087        })
 5088    }
 5089
 5090    fn layout_context_menu_aside(
 5091        &self,
 5092        y_flipped: bool,
 5093        menu_bounds: Bounds<Pixels>,
 5094        target_bounds: Bounds<Pixels>,
 5095        max_target_bounds: Bounds<Pixels>,
 5096        max_height: Pixels,
 5097        must_place_above_or_below: bool,
 5098        text_hitbox: &Hitbox,
 5099        viewport_bounds: Bounds<Pixels>,
 5100        window: &mut Window,
 5101        cx: &mut App,
 5102    ) -> Option<Bounds<Pixels>> {
 5103        let available_within_viewport = target_bounds.space_within(&viewport_bounds);
 5104        let positioned_aside = if available_within_viewport.right >= MENU_ASIDE_MIN_WIDTH
 5105            && !must_place_above_or_below
 5106        {
 5107            let max_width = cmp::min(
 5108                available_within_viewport.right - px(1.),
 5109                MENU_ASIDE_MAX_WIDTH,
 5110            );
 5111            let mut aside = self.render_context_menu_aside(
 5112                size(max_width, max_height - POPOVER_Y_PADDING),
 5113                window,
 5114                cx,
 5115            )?;
 5116            let size = aside.layout_as_root(AvailableSpace::min_size(), window, cx);
 5117            let right_position = point(target_bounds.right(), menu_bounds.origin.y);
 5118            Some((aside, right_position, size))
 5119        } else {
 5120            let max_size = size(
 5121                // TODO(mgsloan): Once the menu is bounded by viewport width the bound on viewport
 5122                // won't be needed here.
 5123                cmp::min(
 5124                    cmp::max(menu_bounds.size.width - px(2.), MENU_ASIDE_MIN_WIDTH),
 5125                    viewport_bounds.right(),
 5126                ),
 5127                cmp::min(
 5128                    max_height,
 5129                    cmp::max(
 5130                        available_within_viewport.top,
 5131                        available_within_viewport.bottom,
 5132                    ),
 5133                ) - POPOVER_Y_PADDING,
 5134            );
 5135            let mut aside = self.render_context_menu_aside(max_size, window, cx)?;
 5136            let actual_size = aside.layout_as_root(AvailableSpace::min_size(), window, cx);
 5137
 5138            let top_position = point(
 5139                menu_bounds.origin.x,
 5140                target_bounds.top() - actual_size.height,
 5141            );
 5142            let bottom_position = point(menu_bounds.origin.x, target_bounds.bottom());
 5143
 5144            let fit_within = |available: Edges<Pixels>, wanted: Size<Pixels>| {
 5145                // Prefer to fit on the same side of the line as the menu, then on the other side of
 5146                // the line.
 5147                if !y_flipped && wanted.height < available.bottom {
 5148                    Some(bottom_position)
 5149                } else if !y_flipped && wanted.height < available.top {
 5150                    Some(top_position)
 5151                } else if y_flipped && wanted.height < available.top {
 5152                    Some(top_position)
 5153                } else if y_flipped && wanted.height < available.bottom {
 5154                    Some(bottom_position)
 5155                } else {
 5156                    None
 5157                }
 5158            };
 5159
 5160            // Prefer choosing a direction using max sizes rather than actual size for stability.
 5161            let available_within_text = max_target_bounds.space_within(&text_hitbox.bounds);
 5162            let wanted = size(MENU_ASIDE_MAX_WIDTH, max_height);
 5163            let aside_position = fit_within(available_within_text, wanted)
 5164                // Fallback: fit max size in window.
 5165                .or_else(|| fit_within(max_target_bounds.space_within(&viewport_bounds), wanted))
 5166                // Fallback: fit actual size in window.
 5167                .or_else(|| fit_within(available_within_viewport, actual_size));
 5168
 5169            aside_position.map(|position| (aside, position, actual_size))
 5170        };
 5171
 5172        // Skip drawing if it doesn't fit anywhere.
 5173        if let Some((aside, position, size)) = positioned_aside {
 5174            let aside_bounds = Bounds::new(position, size);
 5175            window.defer_draw(aside, position, 2, None);
 5176            return Some(aside_bounds);
 5177        }
 5178
 5179        None
 5180    }
 5181
 5182    fn render_context_menu(
 5183        &self,
 5184        line_height: Pixels,
 5185        height: Pixels,
 5186        window: &mut Window,
 5187        cx: &mut App,
 5188    ) -> Option<AnyElement> {
 5189        let max_height_in_lines = ((height - POPOVER_Y_PADDING) / line_height).floor() as u32;
 5190        self.editor.update(cx, |editor, cx| {
 5191            editor.render_context_menu(max_height_in_lines, window, cx)
 5192        })
 5193    }
 5194
 5195    fn render_context_menu_aside(
 5196        &self,
 5197        max_size: Size<Pixels>,
 5198        window: &mut Window,
 5199        cx: &mut App,
 5200    ) -> Option<AnyElement> {
 5201        if max_size.width < px(100.) || max_size.height < px(12.) {
 5202            None
 5203        } else {
 5204            self.editor.update(cx, |editor, cx| {
 5205                editor.render_context_menu_aside(max_size, window, cx)
 5206            })
 5207        }
 5208    }
 5209
 5210    fn layout_mouse_context_menu(
 5211        &self,
 5212        editor_snapshot: &EditorSnapshot,
 5213        visible_range: Range<DisplayRow>,
 5214        content_origin: gpui::Point<Pixels>,
 5215        window: &mut Window,
 5216        cx: &mut App,
 5217    ) -> Option<AnyElement> {
 5218        let position = self.editor.update(cx, |editor, cx| {
 5219            let visible_start_point = editor.display_to_pixel_point(
 5220                DisplayPoint::new(visible_range.start, 0),
 5221                editor_snapshot,
 5222                window,
 5223                cx,
 5224            )?;
 5225            let visible_end_point = editor.display_to_pixel_point(
 5226                DisplayPoint::new(visible_range.end, 0),
 5227                editor_snapshot,
 5228                window,
 5229                cx,
 5230            )?;
 5231
 5232            let mouse_context_menu = editor.mouse_context_menu.as_ref()?;
 5233            let (source_display_point, position) = match mouse_context_menu.position {
 5234                MenuPosition::PinnedToScreen(point) => (None, point),
 5235                MenuPosition::PinnedToEditor { source, offset } => {
 5236                    let source_display_point = source.to_display_point(editor_snapshot);
 5237                    let source_point =
 5238                        editor.to_pixel_point(source, editor_snapshot, window, cx)?;
 5239                    let position = content_origin + source_point + offset;
 5240                    (Some(source_display_point), position)
 5241                }
 5242            };
 5243
 5244            let source_included = source_display_point.is_none_or(|source_display_point| {
 5245                visible_range
 5246                    .to_inclusive()
 5247                    .contains(&source_display_point.row())
 5248            });
 5249            let position_included =
 5250                visible_start_point.y <= position.y && position.y <= visible_end_point.y;
 5251            if !source_included && !position_included {
 5252                None
 5253            } else {
 5254                Some(position)
 5255            }
 5256        })?;
 5257
 5258        let text_style = TextStyleRefinement {
 5259            line_height: Some(DefiniteLength::Fraction(
 5260                BufferLineHeight::Comfortable.value(),
 5261            )),
 5262            ..Default::default()
 5263        };
 5264        window.with_text_style(Some(text_style), |window| {
 5265            let mut element = self.editor.read_with(cx, |editor, _| {
 5266                let mouse_context_menu = editor.mouse_context_menu.as_ref()?;
 5267                let context_menu = mouse_context_menu.context_menu.clone();
 5268
 5269                Some(
 5270                    deferred(
 5271                        anchored()
 5272                            .position(position)
 5273                            .child(context_menu)
 5274                            .anchor(gpui::Anchor::TopLeft)
 5275                            .snap_to_window_with_margin(px(8.)),
 5276                    )
 5277                    .with_priority(1)
 5278                    .into_any(),
 5279                )
 5280            })?;
 5281
 5282            element.prepaint_as_root(position, AvailableSpace::min_size(), window, cx);
 5283            Some(element)
 5284        })
 5285    }
 5286
 5287    fn layout_hover_popovers(
 5288        &self,
 5289        snapshot: &EditorSnapshot,
 5290        hitbox: &Hitbox,
 5291        visible_display_row_range: Range<DisplayRow>,
 5292        content_origin: gpui::Point<Pixels>,
 5293        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 5294        line_layouts: &[LineWithInvisibles],
 5295        line_height: Pixels,
 5296        em_width: Pixels,
 5297        context_menu_layout: Option<ContextMenuLayout>,
 5298        window: &mut Window,
 5299        cx: &mut App,
 5300    ) {
 5301        struct MeasuredHoverPopover {
 5302            element: AnyElement,
 5303            size: Size<Pixels>,
 5304            horizontal_offset: Pixels,
 5305        }
 5306
 5307        let max_size = size(
 5308            (120. * em_width) // Default size
 5309                .min(hitbox.size.width / 2.) // Shrink to half of the editor width
 5310                .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
 5311            (16. * line_height) // Default size
 5312                .min(hitbox.size.height / 2.) // Shrink to half of the editor height
 5313                .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
 5314        );
 5315
 5316        // Don't show hover popovers when context menu is open to avoid overlap
 5317        let has_context_menu = self.editor.read(cx).mouse_context_menu.is_some();
 5318        if has_context_menu {
 5319            return;
 5320        }
 5321
 5322        let hover_popovers = self.editor.update(cx, |editor, cx| {
 5323            editor.hover_state.render(
 5324                snapshot,
 5325                visible_display_row_range.clone(),
 5326                max_size,
 5327                &editor.text_layout_details(window, cx),
 5328                window,
 5329                cx,
 5330            )
 5331        });
 5332        let Some((popover_position, hover_popovers)) = hover_popovers else {
 5333            return;
 5334        };
 5335
 5336        // This is safe because we check on layout whether the required row is available
 5337        let hovered_row_layout = &line_layouts[popover_position
 5338            .row()
 5339            .minus(visible_display_row_range.start)
 5340            as usize];
 5341
 5342        // Compute Hovered Point
 5343        let x = hovered_row_layout.x_for_index(popover_position.column() as usize)
 5344            - Pixels::from(scroll_pixel_position.x);
 5345        let y = Pixels::from(
 5346            popover_position.row().as_f64() * ScrollPixelOffset::from(line_height)
 5347                - scroll_pixel_position.y,
 5348        );
 5349        let hovered_point = content_origin + point(x, y);
 5350
 5351        let mut overall_height = Pixels::ZERO;
 5352        let mut measured_hover_popovers = Vec::new();
 5353        for (position, mut hover_popover) in hover_popovers.into_iter().with_position() {
 5354            let size = hover_popover.layout_as_root(AvailableSpace::min_size(), window, cx);
 5355            let horizontal_offset =
 5356                (hitbox.top_right().x - POPOVER_RIGHT_OFFSET - (hovered_point.x + size.width))
 5357                    .min(Pixels::ZERO);
 5358            match position {
 5359                itertools::Position::Middle | itertools::Position::Last => {
 5360                    overall_height += HOVER_POPOVER_GAP
 5361                }
 5362                _ => {}
 5363            }
 5364            overall_height += size.height;
 5365            measured_hover_popovers.push(MeasuredHoverPopover {
 5366                element: hover_popover,
 5367                size,
 5368                horizontal_offset,
 5369            });
 5370        }
 5371
 5372        fn draw_occluder(
 5373            width: Pixels,
 5374            origin: gpui::Point<Pixels>,
 5375            window: &mut Window,
 5376            cx: &mut App,
 5377        ) {
 5378            let mut occlusion = div()
 5379                .size_full()
 5380                .occlude()
 5381                .on_mouse_move(|_, _, cx| cx.stop_propagation())
 5382                .into_any_element();
 5383            occlusion.layout_as_root(size(width, HOVER_POPOVER_GAP).into(), window, cx);
 5384            window.defer_draw(occlusion, origin, 2, None);
 5385        }
 5386
 5387        fn place_popovers_above(
 5388            hovered_point: gpui::Point<Pixels>,
 5389            measured_hover_popovers: Vec<MeasuredHoverPopover>,
 5390            window: &mut Window,
 5391            cx: &mut App,
 5392        ) {
 5393            let mut current_y = hovered_point.y;
 5394            for (position, popover) in measured_hover_popovers.into_iter().with_position() {
 5395                let size = popover.size;
 5396                let popover_origin = point(
 5397                    hovered_point.x + popover.horizontal_offset,
 5398                    current_y - size.height,
 5399                );
 5400
 5401                window.defer_draw(popover.element, popover_origin, 2, None);
 5402                if position != itertools::Position::Last {
 5403                    let origin = point(popover_origin.x, popover_origin.y - HOVER_POPOVER_GAP);
 5404                    draw_occluder(size.width, origin, window, cx);
 5405                }
 5406
 5407                current_y = popover_origin.y - HOVER_POPOVER_GAP;
 5408            }
 5409        }
 5410
 5411        fn place_popovers_below(
 5412            hovered_point: gpui::Point<Pixels>,
 5413            measured_hover_popovers: Vec<MeasuredHoverPopover>,
 5414            line_height: Pixels,
 5415            window: &mut Window,
 5416            cx: &mut App,
 5417        ) {
 5418            let mut current_y = hovered_point.y + line_height;
 5419            for (position, popover) in measured_hover_popovers.into_iter().with_position() {
 5420                let size = popover.size;
 5421                let popover_origin = point(hovered_point.x + popover.horizontal_offset, current_y);
 5422
 5423                window.defer_draw(popover.element, popover_origin, 2, None);
 5424                if position != itertools::Position::Last {
 5425                    let origin = point(popover_origin.x, popover_origin.y + size.height);
 5426                    draw_occluder(size.width, origin, window, cx);
 5427                }
 5428
 5429                current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
 5430            }
 5431        }
 5432
 5433        let intersects_menu = |bounds: Bounds<Pixels>| -> bool {
 5434            context_menu_layout
 5435                .as_ref()
 5436                .is_some_and(|menu| bounds.intersects(&menu.bounds))
 5437        };
 5438
 5439        let can_place_above = {
 5440            let mut bounds_above = Vec::new();
 5441            let mut current_y = hovered_point.y;
 5442            for popover in &measured_hover_popovers {
 5443                let size = popover.size;
 5444                let popover_origin = point(
 5445                    hovered_point.x + popover.horizontal_offset,
 5446                    current_y - size.height,
 5447                );
 5448                bounds_above.push(Bounds::new(popover_origin, size));
 5449                current_y = popover_origin.y - HOVER_POPOVER_GAP;
 5450            }
 5451            bounds_above
 5452                .iter()
 5453                .all(|b| b.is_contained_within(hitbox) && !intersects_menu(*b))
 5454        };
 5455
 5456        let can_place_below = || {
 5457            let mut bounds_below = Vec::new();
 5458            let mut current_y = hovered_point.y + line_height;
 5459            for popover in &measured_hover_popovers {
 5460                let size = popover.size;
 5461                let popover_origin = point(hovered_point.x + popover.horizontal_offset, current_y);
 5462                bounds_below.push(Bounds::new(popover_origin, size));
 5463                current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
 5464            }
 5465            bounds_below
 5466                .iter()
 5467                .all(|b| b.is_contained_within(hitbox) && !intersects_menu(*b))
 5468        };
 5469
 5470        if can_place_above {
 5471            // try placing above hovered point
 5472            place_popovers_above(hovered_point, measured_hover_popovers, window, cx);
 5473        } else if can_place_below() {
 5474            // try placing below hovered point
 5475            place_popovers_below(
 5476                hovered_point,
 5477                measured_hover_popovers,
 5478                line_height,
 5479                window,
 5480                cx,
 5481            );
 5482        } else {
 5483            // try to place popovers around the context menu
 5484            let origin_surrounding_menu = context_menu_layout.as_ref().and_then(|menu| {
 5485                let total_width = measured_hover_popovers
 5486                    .iter()
 5487                    .map(|p| p.size.width)
 5488                    .max()
 5489                    .unwrap_or(Pixels::ZERO);
 5490                let y_for_horizontal_positioning = if menu.y_flipped {
 5491                    menu.bounds.bottom() - overall_height
 5492                } else {
 5493                    menu.bounds.top()
 5494                };
 5495                let possible_origins = vec![
 5496                    // left of context menu
 5497                    point(
 5498                        menu.bounds.left() - total_width - HOVER_POPOVER_GAP,
 5499                        y_for_horizontal_positioning,
 5500                    ),
 5501                    // right of context menu
 5502                    point(
 5503                        menu.bounds.right() + HOVER_POPOVER_GAP,
 5504                        y_for_horizontal_positioning,
 5505                    ),
 5506                    // top of context menu
 5507                    point(
 5508                        menu.bounds.left(),
 5509                        menu.bounds.top() - overall_height - HOVER_POPOVER_GAP,
 5510                    ),
 5511                    // bottom of context menu
 5512                    point(menu.bounds.left(), menu.bounds.bottom() + HOVER_POPOVER_GAP),
 5513                ];
 5514                possible_origins.into_iter().find(|&origin| {
 5515                    Bounds::new(origin, size(total_width, overall_height))
 5516                        .is_contained_within(hitbox)
 5517                })
 5518            });
 5519            if let Some(origin) = origin_surrounding_menu {
 5520                let mut current_y = origin.y;
 5521                for (position, popover) in measured_hover_popovers.into_iter().with_position() {
 5522                    let size = popover.size;
 5523                    let popover_origin = point(origin.x, current_y);
 5524
 5525                    window.defer_draw(popover.element, popover_origin, 2, None);
 5526                    if position != itertools::Position::Last {
 5527                        let origin = point(popover_origin.x, popover_origin.y + size.height);
 5528                        draw_occluder(size.width, origin, window, cx);
 5529                    }
 5530
 5531                    current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
 5532                }
 5533            } else {
 5534                // fallback to existing above/below cursor logic
 5535                // this might overlap menu or overflow in rare case
 5536                if can_place_above {
 5537                    place_popovers_above(hovered_point, measured_hover_popovers, window, cx);
 5538                } else {
 5539                    place_popovers_below(
 5540                        hovered_point,
 5541                        measured_hover_popovers,
 5542                        line_height,
 5543                        window,
 5544                        cx,
 5545                    );
 5546                }
 5547            }
 5548        }
 5549    }
 5550
 5551    fn layout_word_diff_highlights(
 5552        display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
 5553        row_infos: &[RowInfo],
 5554        start_row: DisplayRow,
 5555        snapshot: &EditorSnapshot,
 5556        highlighted_ranges: &mut Vec<(Range<DisplayPoint>, Hsla)>,
 5557        cx: &mut App,
 5558    ) {
 5559        let colors = cx.theme().colors();
 5560
 5561        let word_highlights = display_hunks
 5562            .into_iter()
 5563            .filter_map(|(hunk, _)| match hunk {
 5564                DisplayDiffHunk::Unfolded {
 5565                    word_diffs, status, ..
 5566                } => Some((word_diffs, status)),
 5567                _ => None,
 5568            })
 5569            .filter(|(_, status)| status.is_modified())
 5570            .flat_map(|(word_diffs, _)| word_diffs)
 5571            .flat_map(|word_diff| {
 5572                let display_ranges = snapshot
 5573                    .display_snapshot
 5574                    .isomorphic_display_point_ranges_for_buffer_range(
 5575                        word_diff.start..word_diff.end,
 5576                    );
 5577
 5578                display_ranges.into_iter().filter_map(|range| {
 5579                    let start_row_offset = range.start.row().0.saturating_sub(start_row.0) as usize;
 5580
 5581                    let diff_status = row_infos
 5582                        .get(start_row_offset)
 5583                        .and_then(|row_info| row_info.diff_status)?;
 5584
 5585                    let background_color = match diff_status.kind {
 5586                        DiffHunkStatusKind::Added => colors.version_control_word_added,
 5587                        DiffHunkStatusKind::Deleted => colors.version_control_word_deleted,
 5588                        DiffHunkStatusKind::Modified => {
 5589                            debug_panic!("modified diff status for row info");
 5590                            return None;
 5591                        }
 5592                    };
 5593
 5594                    Some((range, background_color))
 5595                })
 5596            });
 5597
 5598        highlighted_ranges.extend(word_highlights);
 5599    }
 5600
 5601    fn layout_diff_hunk_controls(
 5602        &self,
 5603        row_range: Range<DisplayRow>,
 5604        row_infos: &[RowInfo],
 5605        text_hitbox: &Hitbox,
 5606        newest_cursor_row: Option<DisplayRow>,
 5607        line_height: Pixels,
 5608        right_margin: Pixels,
 5609        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 5610        sticky_header_height: Pixels,
 5611        display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
 5612        highlighted_rows: &BTreeMap<DisplayRow, LineHighlight>,
 5613        editor: Entity<Editor>,
 5614        window: &mut Window,
 5615        cx: &mut App,
 5616    ) -> (Vec<AnyElement>, Vec<(DisplayRow, Bounds<Pixels>)>) {
 5617        let render_diff_hunk_controls = editor.read(cx).render_diff_hunk_controls.clone();
 5618        let hovered_diff_hunk_row = editor.read(cx).hovered_diff_hunk_row;
 5619        let sticky_top = text_hitbox.bounds.top() + sticky_header_height;
 5620
 5621        let mut controls = vec![];
 5622        let mut control_bounds = vec![];
 5623
 5624        let active_rows = [hovered_diff_hunk_row, newest_cursor_row];
 5625
 5626        for (hunk, _) in display_hunks {
 5627            if let DisplayDiffHunk::Unfolded {
 5628                display_row_range,
 5629                multi_buffer_range,
 5630                status,
 5631                is_created_file,
 5632                ..
 5633            } = &hunk
 5634            {
 5635                if display_row_range.start >= row_range.end {
 5636                    // hunk is fully below the viewport
 5637                    continue;
 5638                }
 5639                if display_row_range.end <= row_range.start {
 5640                    // hunk is fully above the viewport
 5641                    continue;
 5642                }
 5643                let row_ix = display_row_range.start.0.saturating_sub(row_range.start.0);
 5644                if row_infos
 5645                    .get(row_ix as usize)
 5646                    .and_then(|row_info| row_info.diff_status)
 5647                    .is_none()
 5648                {
 5649                    continue;
 5650                }
 5651                if highlighted_rows
 5652                    .get(&display_row_range.start)
 5653                    .and_then(|highlight| highlight.type_id)
 5654                    .is_some_and(|type_id| {
 5655                        [
 5656                            TypeId::of::<ConflictsOuter>(),
 5657                            TypeId::of::<ConflictsOursMarker>(),
 5658                            TypeId::of::<ConflictsOurs>(),
 5659                            TypeId::of::<ConflictsTheirs>(),
 5660                            TypeId::of::<ConflictsTheirsMarker>(),
 5661                        ]
 5662                        .contains(&type_id)
 5663                    })
 5664                {
 5665                    continue;
 5666                }
 5667
 5668                if active_rows
 5669                    .iter()
 5670                    .any(|row| row.is_some_and(|row| display_row_range.contains(&row)))
 5671                {
 5672                    let hunk_start_y: Pixels = (display_row_range.start.as_f64()
 5673                        * ScrollPixelOffset::from(line_height)
 5674                        + ScrollPixelOffset::from(text_hitbox.bounds.top())
 5675                        - scroll_pixel_position.y)
 5676                        .into();
 5677
 5678                    let y: Pixels = if hunk_start_y >= sticky_top {
 5679                        hunk_start_y
 5680                    } else {
 5681                        let hunk_end_y: Pixels = hunk_start_y
 5682                            + (display_row_range.len() as f64
 5683                                * ScrollPixelOffset::from(line_height))
 5684                            .into();
 5685                        let max_y = hunk_end_y - line_height;
 5686                        sticky_top.min(max_y)
 5687                    };
 5688
 5689                    let mut element = render_diff_hunk_controls(
 5690                        display_row_range.start.0,
 5691                        status,
 5692                        multi_buffer_range.clone(),
 5693                        *is_created_file,
 5694                        line_height,
 5695                        &editor,
 5696                        window,
 5697                        cx,
 5698                    );
 5699                    let size =
 5700                        element.layout_as_root(size(px(100.0), line_height).into(), window, cx);
 5701
 5702                    let x = text_hitbox.bounds.right() - right_margin - px(10.) - size.width;
 5703
 5704                    if x < text_hitbox.bounds.left() {
 5705                        continue;
 5706                    }
 5707
 5708                    let bounds = Bounds::new(gpui::Point::new(x, y), size);
 5709                    control_bounds.push((display_row_range.start, bounds));
 5710
 5711                    window.with_absolute_element_offset(gpui::Point::new(x, y), |window| {
 5712                        element.prepaint(window, cx)
 5713                    });
 5714                    controls.push(element);
 5715                }
 5716            }
 5717        }
 5718
 5719        (controls, control_bounds)
 5720    }
 5721
 5722    fn layout_signature_help(
 5723        &self,
 5724        hitbox: &Hitbox,
 5725        content_origin: gpui::Point<Pixels>,
 5726        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 5727        newest_selection_head: Option<DisplayPoint>,
 5728        start_row: DisplayRow,
 5729        line_layouts: &[LineWithInvisibles],
 5730        line_height: Pixels,
 5731        em_width: Pixels,
 5732        context_menu_layout: Option<ContextMenuLayout>,
 5733        window: &mut Window,
 5734        cx: &mut App,
 5735    ) {
 5736        if !self.editor.focus_handle(cx).is_focused(window) {
 5737            return;
 5738        }
 5739        let Some(newest_selection_head) = newest_selection_head else {
 5740            return;
 5741        };
 5742
 5743        let max_size = size(
 5744            (120. * em_width) // Default size
 5745                .min(hitbox.size.width / 2.) // Shrink to half of the editor width
 5746                .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
 5747            (16. * line_height) // Default size
 5748                .min(hitbox.size.height / 2.) // Shrink to half of the editor height
 5749                .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
 5750        );
 5751
 5752        let maybe_element = self.editor.update(cx, |editor, cx| {
 5753            if let Some(popover) = editor.signature_help_state.popover_mut() {
 5754                let element = popover.render(max_size, window, cx);
 5755                Some(element)
 5756            } else {
 5757                None
 5758            }
 5759        });
 5760        let Some(mut element) = maybe_element else {
 5761            return;
 5762        };
 5763
 5764        let selection_row = newest_selection_head.row();
 5765        let Some(cursor_row_layout) = (selection_row >= start_row)
 5766            .then(|| line_layouts.get(selection_row.minus(start_row) as usize))
 5767            .flatten()
 5768        else {
 5769            return;
 5770        };
 5771
 5772        let target_x = cursor_row_layout.x_for_index(newest_selection_head.column() as usize)
 5773            - Pixels::from(scroll_pixel_position.x);
 5774        let target_y = Pixels::from(
 5775            selection_row.as_f64() * ScrollPixelOffset::from(line_height) - scroll_pixel_position.y,
 5776        );
 5777        let target_point = content_origin + point(target_x, target_y);
 5778
 5779        let actual_size = element.layout_as_root(Size::<AvailableSpace>::default(), window, cx);
 5780
 5781        let (popover_bounds_above, popover_bounds_below) = {
 5782            let horizontal_offset = (hitbox.top_right().x
 5783                - POPOVER_RIGHT_OFFSET
 5784                - (target_point.x + actual_size.width))
 5785                .min(Pixels::ZERO);
 5786            let initial_x = target_point.x + horizontal_offset;
 5787            (
 5788                Bounds::new(
 5789                    point(initial_x, target_point.y - actual_size.height),
 5790                    actual_size,
 5791                ),
 5792                Bounds::new(
 5793                    point(initial_x, target_point.y + line_height + HOVER_POPOVER_GAP),
 5794                    actual_size,
 5795                ),
 5796            )
 5797        };
 5798
 5799        let intersects_menu = |bounds: Bounds<Pixels>| -> bool {
 5800            context_menu_layout
 5801                .as_ref()
 5802                .is_some_and(|menu| bounds.intersects(&menu.bounds))
 5803        };
 5804
 5805        let final_origin = if popover_bounds_above.is_contained_within(hitbox)
 5806            && !intersects_menu(popover_bounds_above)
 5807        {
 5808            // try placing above cursor
 5809            popover_bounds_above.origin
 5810        } else if popover_bounds_below.is_contained_within(hitbox)
 5811            && !intersects_menu(popover_bounds_below)
 5812        {
 5813            // try placing below cursor
 5814            popover_bounds_below.origin
 5815        } else {
 5816            // try surrounding context menu if exists
 5817            let origin_surrounding_menu = context_menu_layout.as_ref().and_then(|menu| {
 5818                let y_for_horizontal_positioning = if menu.y_flipped {
 5819                    menu.bounds.bottom() - actual_size.height
 5820                } else {
 5821                    menu.bounds.top()
 5822                };
 5823                let possible_origins = vec![
 5824                    // left of context menu
 5825                    point(
 5826                        menu.bounds.left() - actual_size.width - HOVER_POPOVER_GAP,
 5827                        y_for_horizontal_positioning,
 5828                    ),
 5829                    // right of context menu
 5830                    point(
 5831                        menu.bounds.right() + HOVER_POPOVER_GAP,
 5832                        y_for_horizontal_positioning,
 5833                    ),
 5834                    // top of context menu
 5835                    point(
 5836                        menu.bounds.left(),
 5837                        menu.bounds.top() - actual_size.height - HOVER_POPOVER_GAP,
 5838                    ),
 5839                    // bottom of context menu
 5840                    point(menu.bounds.left(), menu.bounds.bottom() + HOVER_POPOVER_GAP),
 5841                ];
 5842                possible_origins
 5843                    .into_iter()
 5844                    .find(|&origin| Bounds::new(origin, actual_size).is_contained_within(hitbox))
 5845            });
 5846            origin_surrounding_menu.unwrap_or_else(|| {
 5847                // fallback to existing above/below cursor logic
 5848                // this might overlap menu or overflow in rare case
 5849                if popover_bounds_above.is_contained_within(hitbox) {
 5850                    popover_bounds_above.origin
 5851                } else {
 5852                    popover_bounds_below.origin
 5853                }
 5854            })
 5855        };
 5856
 5857        window.defer_draw(element, final_origin, 2, None);
 5858    }
 5859
 5860    fn paint_background(&self, layout: &EditorLayout, window: &mut Window, cx: &mut App) {
 5861        window.paint_layer(layout.hitbox.bounds, |window| {
 5862            let scroll_top = layout.position_map.snapshot.scroll_position().y;
 5863            let gutter_bg = cx.theme().colors().editor_gutter_background;
 5864            window.paint_quad(fill(layout.gutter_hitbox.bounds, gutter_bg));
 5865            window.paint_quad(fill(
 5866                layout.position_map.text_hitbox.bounds,
 5867                self.style.background,
 5868            ));
 5869
 5870            if matches!(
 5871                layout.mode,
 5872                EditorMode::Full { .. } | EditorMode::Minimap { .. }
 5873            ) {
 5874                let show_active_line_background = match layout.mode {
 5875                    EditorMode::Full {
 5876                        show_active_line_background,
 5877                        ..
 5878                    } => show_active_line_background,
 5879                    EditorMode::Minimap { .. } => true,
 5880                    _ => false,
 5881                };
 5882                let mut active_rows = layout.active_rows.iter().peekable();
 5883                while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
 5884                    let mut end_row = start_row.0;
 5885                    while active_rows
 5886                        .peek()
 5887                        .is_some_and(|(active_row, has_selection)| {
 5888                            active_row.0 == end_row + 1
 5889                                && has_selection.selection == contains_non_empty_selection.selection
 5890                        })
 5891                    {
 5892                        active_rows.next().unwrap();
 5893                        end_row += 1;
 5894                    }
 5895
 5896                    if show_active_line_background && !contains_non_empty_selection.selection {
 5897                        let highlight_h_range =
 5898                            match layout.position_map.snapshot.current_line_highlight {
 5899                                CurrentLineHighlight::Gutter => Some(Range {
 5900                                    start: layout.hitbox.left(),
 5901                                    end: layout.gutter_hitbox.right(),
 5902                                }),
 5903                                CurrentLineHighlight::Line => Some(Range {
 5904                                    start: layout.position_map.text_hitbox.bounds.left(),
 5905                                    end: layout.position_map.text_hitbox.bounds.right(),
 5906                                }),
 5907                                CurrentLineHighlight::All => Some(Range {
 5908                                    start: layout.hitbox.left(),
 5909                                    end: layout.hitbox.right(),
 5910                                }),
 5911                                CurrentLineHighlight::None => None,
 5912                            };
 5913                        if let Some(range) = highlight_h_range {
 5914                            let active_line_bg = cx.theme().colors().editor_active_line_background;
 5915                            let bounds = Bounds {
 5916                                origin: point(
 5917                                    range.start,
 5918                                    layout.hitbox.origin.y
 5919                                        + Pixels::from(
 5920                                            (start_row.as_f64() - scroll_top)
 5921                                                * ScrollPixelOffset::from(
 5922                                                    layout.position_map.line_height,
 5923                                                ),
 5924                                        ),
 5925                                ),
 5926                                size: size(
 5927                                    range.end - range.start,
 5928                                    layout.position_map.line_height
 5929                                        * (end_row - start_row.0 + 1) as f32,
 5930                                ),
 5931                            };
 5932                            window.paint_quad(fill(bounds, active_line_bg));
 5933                        }
 5934                    }
 5935                }
 5936
 5937                let mut paint_highlight = |highlight_row_start: DisplayRow,
 5938                                           highlight_row_end: DisplayRow,
 5939                                           highlight: crate::LineHighlight,
 5940                                           edges| {
 5941                    let mut origin_x = layout.hitbox.left();
 5942                    let mut width = layout.hitbox.size.width;
 5943                    if !highlight.include_gutter {
 5944                        origin_x += layout.gutter_hitbox.size.width;
 5945                        width -= layout.gutter_hitbox.size.width;
 5946                    }
 5947
 5948                    let origin = point(
 5949                        origin_x,
 5950                        layout.hitbox.origin.y
 5951                            + Pixels::from(
 5952                                (highlight_row_start.as_f64() - scroll_top)
 5953                                    * ScrollPixelOffset::from(layout.position_map.line_height),
 5954                            ),
 5955                    );
 5956                    let size = size(
 5957                        width,
 5958                        layout.position_map.line_height
 5959                            * highlight_row_end.next_row().minus(highlight_row_start) as f32,
 5960                    );
 5961                    let mut quad = fill(Bounds { origin, size }, highlight.background);
 5962                    if let Some(border_color) = highlight.border {
 5963                        quad.border_color = border_color;
 5964                        quad.border_widths = edges
 5965                    }
 5966                    window.paint_quad(quad);
 5967                };
 5968
 5969                let mut current_paint: Option<(LineHighlight, Range<DisplayRow>, Edges<Pixels>)> =
 5970                    None;
 5971                for (&new_row, &new_background) in &layout.highlighted_rows {
 5972                    match &mut current_paint {
 5973                        &mut Some((current_background, ref mut current_range, mut edges)) => {
 5974                            let new_range_started = current_background != new_background
 5975                                || current_range.end.next_row() != new_row;
 5976                            if new_range_started {
 5977                                if current_range.end.next_row() == new_row {
 5978                                    edges.bottom = px(0.);
 5979                                };
 5980                                paint_highlight(
 5981                                    current_range.start,
 5982                                    current_range.end,
 5983                                    current_background,
 5984                                    edges,
 5985                                );
 5986                                let edges = Edges {
 5987                                    top: if current_range.end.next_row() != new_row {
 5988                                        px(1.)
 5989                                    } else {
 5990                                        px(0.)
 5991                                    },
 5992                                    bottom: px(1.),
 5993                                    ..Default::default()
 5994                                };
 5995                                current_paint = Some((new_background, new_row..new_row, edges));
 5996                                continue;
 5997                            } else {
 5998                                current_range.end = current_range.end.next_row();
 5999                            }
 6000                        }
 6001                        None => {
 6002                            let edges = Edges {
 6003                                top: px(1.),
 6004                                bottom: px(1.),
 6005                                ..Default::default()
 6006                            };
 6007                            current_paint = Some((new_background, new_row..new_row, edges))
 6008                        }
 6009                    };
 6010                }
 6011                if let Some((color, range, edges)) = current_paint {
 6012                    paint_highlight(range.start, range.end, color, edges);
 6013                }
 6014
 6015                for (guide_x, active) in layout.wrap_guides.iter() {
 6016                    let color = if *active {
 6017                        cx.theme().colors().editor_active_wrap_guide
 6018                    } else {
 6019                        cx.theme().colors().editor_wrap_guide
 6020                    };
 6021                    window.paint_quad(fill(
 6022                        Bounds {
 6023                            origin: point(*guide_x, layout.position_map.text_hitbox.origin.y),
 6024                            size: size(px(1.), layout.position_map.text_hitbox.size.height),
 6025                        },
 6026                        color,
 6027                    ));
 6028                }
 6029            }
 6030        })
 6031    }
 6032
 6033    fn paint_indent_guides(
 6034        &mut self,
 6035        layout: &mut EditorLayout,
 6036        window: &mut Window,
 6037        cx: &mut App,
 6038    ) {
 6039        let Some(indent_guides) = &layout.indent_guides else {
 6040            return;
 6041        };
 6042
 6043        let faded_color = |color: Hsla, alpha: f32| {
 6044            let mut faded = color;
 6045            faded.a = alpha;
 6046            faded
 6047        };
 6048
 6049        for indent_guide in indent_guides {
 6050            let indent_accent_colors = cx.theme().accents().color_for_index(indent_guide.depth);
 6051            let settings = &indent_guide.settings;
 6052
 6053            // TODO fixed for now, expose them through themes later
 6054            const INDENT_AWARE_ALPHA: f32 = 0.2;
 6055            const INDENT_AWARE_ACTIVE_ALPHA: f32 = 0.4;
 6056            const INDENT_AWARE_BACKGROUND_ALPHA: f32 = 0.1;
 6057            const INDENT_AWARE_BACKGROUND_ACTIVE_ALPHA: f32 = 0.2;
 6058
 6059            let line_color = match (settings.coloring, indent_guide.active) {
 6060                (IndentGuideColoring::Disabled, _) => None,
 6061                (IndentGuideColoring::Fixed, false) => {
 6062                    Some(cx.theme().colors().editor_indent_guide)
 6063                }
 6064                (IndentGuideColoring::Fixed, true) => {
 6065                    Some(cx.theme().colors().editor_indent_guide_active)
 6066                }
 6067                (IndentGuideColoring::IndentAware, false) => {
 6068                    Some(faded_color(indent_accent_colors, INDENT_AWARE_ALPHA))
 6069                }
 6070                (IndentGuideColoring::IndentAware, true) => {
 6071                    Some(faded_color(indent_accent_colors, INDENT_AWARE_ACTIVE_ALPHA))
 6072                }
 6073            };
 6074
 6075            let background_color = match (settings.background_coloring, indent_guide.active) {
 6076                (IndentGuideBackgroundColoring::Disabled, _) => None,
 6077                (IndentGuideBackgroundColoring::IndentAware, false) => Some(faded_color(
 6078                    indent_accent_colors,
 6079                    INDENT_AWARE_BACKGROUND_ALPHA,
 6080                )),
 6081                (IndentGuideBackgroundColoring::IndentAware, true) => Some(faded_color(
 6082                    indent_accent_colors,
 6083                    INDENT_AWARE_BACKGROUND_ACTIVE_ALPHA,
 6084                )),
 6085            };
 6086
 6087            let mut line_indicator_width = 0.;
 6088            if let Some(requested_line_width) = settings.visible_line_width(indent_guide.active) {
 6089                if let Some(color) = line_color {
 6090                    window.paint_quad(fill(
 6091                        Bounds {
 6092                            origin: indent_guide.origin,
 6093                            size: size(px(requested_line_width as f32), indent_guide.length),
 6094                        },
 6095                        color,
 6096                    ));
 6097                    line_indicator_width = requested_line_width as f32;
 6098                }
 6099            }
 6100
 6101            if let Some(color) = background_color {
 6102                let width = indent_guide.single_indent_width - px(line_indicator_width);
 6103                window.paint_quad(fill(
 6104                    Bounds {
 6105                        origin: point(
 6106                            indent_guide.origin.x + px(line_indicator_width),
 6107                            indent_guide.origin.y,
 6108                        ),
 6109                        size: size(width, indent_guide.length),
 6110                    },
 6111                    color,
 6112                ));
 6113            }
 6114        }
 6115    }
 6116
 6117    fn paint_line_numbers(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
 6118        let is_singleton = self.editor.read(cx).buffer_kind(cx) == ItemBufferKind::Singleton;
 6119
 6120        let line_height = layout.position_map.line_height;
 6121        window.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox);
 6122
 6123        for line_layout in layout.line_numbers.values() {
 6124            for LineNumberSegment {
 6125                shaped_line,
 6126                hitbox,
 6127            } in &line_layout.segments
 6128            {
 6129                let Some(hitbox) = hitbox else {
 6130                    continue;
 6131                };
 6132
 6133                let Some(()) = (if !is_singleton && hitbox.is_hovered(window) {
 6134                    let color = cx.theme().colors().editor_hover_line_number;
 6135
 6136                    let line = self.shape_line_number(shaped_line.text.clone(), color, window);
 6137                    line.paint(
 6138                        hitbox.origin,
 6139                        line_height,
 6140                        TextAlign::Left,
 6141                        None,
 6142                        window,
 6143                        cx,
 6144                    )
 6145                    .log_err()
 6146                } else {
 6147                    shaped_line
 6148                        .paint(
 6149                            hitbox.origin,
 6150                            line_height,
 6151                            TextAlign::Left,
 6152                            None,
 6153                            window,
 6154                            cx,
 6155                        )
 6156                        .log_err()
 6157                }) else {
 6158                    continue;
 6159                };
 6160
 6161                // In singleton buffers, we select corresponding lines on the line number click, so use | -like cursor.
 6162                // In multi buffers, we open file at the line number clicked, so use a pointing hand cursor.
 6163                if is_singleton {
 6164                    window.set_cursor_style(CursorStyle::IBeam, hitbox);
 6165                } else {
 6166                    window.set_cursor_style(CursorStyle::PointingHand, hitbox);
 6167                }
 6168            }
 6169        }
 6170    }
 6171
 6172    fn paint_gutter_diff_hunks(
 6173        layout: &mut EditorLayout,
 6174        split_side: Option<SplitSide>,
 6175        window: &mut Window,
 6176        cx: &mut App,
 6177    ) {
 6178        if layout.display_hunks.is_empty() {
 6179            return;
 6180        }
 6181
 6182        let line_height = layout.position_map.line_height;
 6183        window.paint_layer(layout.gutter_hitbox.bounds, |window| {
 6184            for (hunk, hitbox) in &layout.display_hunks {
 6185                let hunk_to_paint = match hunk {
 6186                    DisplayDiffHunk::Folded { .. } => {
 6187                        let hunk_bounds = Self::diff_hunk_bounds(
 6188                            &layout.position_map.snapshot,
 6189                            line_height,
 6190                            layout.gutter_hitbox.bounds,
 6191                            hunk,
 6192                        );
 6193                        Some((
 6194                            hunk_bounds,
 6195                            cx.theme().colors().version_control_modified,
 6196                            Corners::all(px(0.)),
 6197                            DiffHunkStatus::modified_none(),
 6198                        ))
 6199                    }
 6200                    DisplayDiffHunk::Unfolded {
 6201                        status,
 6202                        display_row_range,
 6203                        ..
 6204                    } => hitbox.as_ref().map(|hunk_hitbox| {
 6205                        let color = match split_side {
 6206                            Some(SplitSide::Left) => cx.theme().colors().version_control_deleted,
 6207                            Some(SplitSide::Right) => cx.theme().colors().version_control_added,
 6208                            None => match status.kind {
 6209                                DiffHunkStatusKind::Added => {
 6210                                    cx.theme().colors().version_control_added
 6211                                }
 6212                                DiffHunkStatusKind::Modified => {
 6213                                    cx.theme().colors().version_control_modified
 6214                                }
 6215                                DiffHunkStatusKind::Deleted => {
 6216                                    cx.theme().colors().version_control_deleted
 6217                                }
 6218                            },
 6219                        };
 6220                        match status.kind {
 6221                            DiffHunkStatusKind::Deleted if display_row_range.is_empty() => (
 6222                                Bounds::new(
 6223                                    point(
 6224                                        hunk_hitbox.origin.x - hunk_hitbox.size.width,
 6225                                        hunk_hitbox.origin.y,
 6226                                    ),
 6227                                    size(hunk_hitbox.size.width * 2., hunk_hitbox.size.height),
 6228                                ),
 6229                                color,
 6230                                Corners::all(1. * line_height),
 6231                                *status,
 6232                            ),
 6233                            _ => (hunk_hitbox.bounds, color, Corners::all(px(0.)), *status),
 6234                        }
 6235                    }),
 6236                };
 6237
 6238                if let Some((hunk_bounds, background_color, corner_radii, status)) = hunk_to_paint {
 6239                    // Flatten the background color with the editor color to prevent
 6240                    // elements below transparent hunks from showing through
 6241                    let flattened_background_color = cx
 6242                        .theme()
 6243                        .colors()
 6244                        .editor_background
 6245                        .blend(background_color);
 6246
 6247                    if !Self::diff_hunk_hollow(status, cx) {
 6248                        window.paint_quad(quad(
 6249                            hunk_bounds,
 6250                            corner_radii,
 6251                            flattened_background_color,
 6252                            Edges::default(),
 6253                            transparent_black(),
 6254                            BorderStyle::default(),
 6255                        ));
 6256                    } else {
 6257                        let flattened_unstaged_background_color = cx
 6258                            .theme()
 6259                            .colors()
 6260                            .editor_background
 6261                            .blend(background_color.opacity(0.3));
 6262
 6263                        window.paint_quad(quad(
 6264                            hunk_bounds,
 6265                            corner_radii,
 6266                            flattened_unstaged_background_color,
 6267                            Edges::all(px(1.0)),
 6268                            flattened_background_color,
 6269                            BorderStyle::Solid,
 6270                        ));
 6271                    }
 6272                }
 6273            }
 6274        });
 6275    }
 6276
 6277    fn gutter_strip_width(line_height: Pixels) -> Pixels {
 6278        (0.275 * line_height).floor()
 6279    }
 6280
 6281    fn diff_hunk_bounds(
 6282        snapshot: &EditorSnapshot,
 6283        line_height: Pixels,
 6284        gutter_bounds: Bounds<Pixels>,
 6285        hunk: &DisplayDiffHunk,
 6286    ) -> Bounds<Pixels> {
 6287        let scroll_position = snapshot.scroll_position();
 6288        let scroll_top = scroll_position.y * ScrollPixelOffset::from(line_height);
 6289        let gutter_strip_width = Self::gutter_strip_width(line_height);
 6290
 6291        match hunk {
 6292            DisplayDiffHunk::Folded { display_row, .. } => {
 6293                let start_y = (display_row.as_f64() * ScrollPixelOffset::from(line_height)
 6294                    - scroll_top)
 6295                    .into();
 6296                let end_y = start_y + line_height;
 6297                let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
 6298                let highlight_size = size(gutter_strip_width, end_y - start_y);
 6299                Bounds::new(highlight_origin, highlight_size)
 6300            }
 6301            DisplayDiffHunk::Unfolded {
 6302                display_row_range,
 6303                status,
 6304                ..
 6305            } => {
 6306                if status.is_deleted() && display_row_range.is_empty() {
 6307                    let row = display_row_range.start;
 6308
 6309                    let offset = ScrollPixelOffset::from(line_height / 2.);
 6310                    let start_y =
 6311                        (row.as_f64() * ScrollPixelOffset::from(line_height) - offset - scroll_top)
 6312                            .into();
 6313                    let end_y = start_y + line_height;
 6314
 6315                    let width = (0.35 * line_height).floor();
 6316                    let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
 6317                    let highlight_size = size(width, end_y - start_y);
 6318                    Bounds::new(highlight_origin, highlight_size)
 6319                } else {
 6320                    let start_row = display_row_range.start;
 6321                    let end_row = display_row_range.end;
 6322                    // If we're in a multibuffer, row range span might include an
 6323                    // excerpt header, so if we were to draw the marker straight away,
 6324                    // the hunk might include the rows of that header.
 6325                    // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap.
 6326                    // Instead, we simply check whether the range we're dealing with includes
 6327                    // any excerpt headers and if so, we stop painting the diff hunk on the first row of that header.
 6328                    let end_row_in_current_excerpt = snapshot
 6329                        .blocks_in_range(start_row..end_row)
 6330                        .find_map(|(start_row, block)| {
 6331                            if matches!(
 6332                                block,
 6333                                Block::ExcerptBoundary { .. } | Block::BufferHeader { .. }
 6334                            ) {
 6335                                Some(start_row)
 6336                            } else {
 6337                                None
 6338                            }
 6339                        })
 6340                        .unwrap_or(end_row);
 6341
 6342                    let start_y = (start_row.as_f64() * ScrollPixelOffset::from(line_height)
 6343                        - scroll_top)
 6344                        .into();
 6345                    let end_y = Pixels::from(
 6346                        end_row_in_current_excerpt.as_f64() * ScrollPixelOffset::from(line_height)
 6347                            - scroll_top,
 6348                    );
 6349
 6350                    let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
 6351                    let highlight_size = size(gutter_strip_width, end_y - start_y);
 6352                    Bounds::new(highlight_origin, highlight_size)
 6353                }
 6354            }
 6355        }
 6356    }
 6357
 6358    fn paint_gutter_indicators(
 6359        &self,
 6360        layout: &mut EditorLayout,
 6361        window: &mut Window,
 6362        cx: &mut App,
 6363    ) {
 6364        window.paint_layer(layout.gutter_hitbox.bounds, |window| {
 6365            window.with_element_namespace("crease_toggles", |window| {
 6366                for crease_toggle in layout.crease_toggles.iter_mut().flatten() {
 6367                    crease_toggle.paint(window, cx);
 6368                }
 6369            });
 6370
 6371            window.with_element_namespace("expand_toggles", |window| {
 6372                for (expand_toggle, _) in layout.expand_toggles.iter_mut().flatten() {
 6373                    expand_toggle.paint(window, cx);
 6374                }
 6375            });
 6376
 6377            for bookmark in layout.bookmarks.iter_mut() {
 6378                bookmark.paint(window, cx);
 6379            }
 6380
 6381            for breakpoint in layout.breakpoints.iter_mut() {
 6382                breakpoint.paint(window, cx);
 6383            }
 6384
 6385            for test_indicator in layout.test_indicators.iter_mut() {
 6386                test_indicator.paint(window, cx);
 6387            }
 6388
 6389            if let Some(diff_review_button) = layout.diff_review_button.as_mut() {
 6390                diff_review_button.paint(window, cx);
 6391            }
 6392        });
 6393    }
 6394
 6395    fn paint_gutter_highlights(
 6396        &self,
 6397        layout: &mut EditorLayout,
 6398        window: &mut Window,
 6399        cx: &mut App,
 6400    ) {
 6401        for (_, hunk_hitbox) in &layout.display_hunks {
 6402            if let Some(hunk_hitbox) = hunk_hitbox
 6403                && !self
 6404                    .editor
 6405                    .read(cx)
 6406                    .buffer()
 6407                    .read(cx)
 6408                    .all_diff_hunks_expanded()
 6409            {
 6410                window.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox);
 6411            }
 6412        }
 6413
 6414        let show_git_gutter = layout
 6415            .position_map
 6416            .snapshot
 6417            .show_git_diff_gutter
 6418            .unwrap_or_else(|| {
 6419                matches!(
 6420                    ProjectSettings::get_global(cx).git.git_gutter,
 6421                    GitGutterSetting::TrackedFiles
 6422                )
 6423            });
 6424        if show_git_gutter {
 6425            Self::paint_gutter_diff_hunks(layout, self.split_side, window, cx)
 6426        }
 6427
 6428        let highlight_width = 0.275 * layout.position_map.line_height;
 6429        let highlight_corner_radii = Corners::all(0.05 * layout.position_map.line_height);
 6430        window.paint_layer(layout.gutter_hitbox.bounds, |window| {
 6431            for (range, color) in &layout.highlighted_gutter_ranges {
 6432                let start_row = if range.start.row() < layout.visible_display_row_range.start {
 6433                    layout.visible_display_row_range.start - DisplayRow(1)
 6434                } else {
 6435                    range.start.row()
 6436                };
 6437                let end_row = if range.end.row() > layout.visible_display_row_range.end {
 6438                    layout.visible_display_row_range.end + DisplayRow(1)
 6439                } else {
 6440                    range.end.row()
 6441                };
 6442
 6443                let start_y = layout.gutter_hitbox.top()
 6444                    + Pixels::from(
 6445                        start_row.0 as f64
 6446                            * ScrollPixelOffset::from(layout.position_map.line_height)
 6447                            - layout.position_map.scroll_pixel_position.y,
 6448                    );
 6449                let end_y = layout.gutter_hitbox.top()
 6450                    + Pixels::from(
 6451                        (end_row.0 + 1) as f64
 6452                            * ScrollPixelOffset::from(layout.position_map.line_height)
 6453                            - layout.position_map.scroll_pixel_position.y,
 6454                    );
 6455                let bounds = Bounds::from_corners(
 6456                    point(layout.gutter_hitbox.left(), start_y),
 6457                    point(layout.gutter_hitbox.left() + highlight_width, end_y),
 6458                );
 6459                window.paint_quad(fill(bounds, *color).corner_radii(highlight_corner_radii));
 6460            }
 6461        });
 6462    }
 6463
 6464    fn paint_blamed_display_rows(
 6465        &self,
 6466        layout: &mut EditorLayout,
 6467        window: &mut Window,
 6468        cx: &mut App,
 6469    ) {
 6470        let Some(blamed_display_rows) = layout.blamed_display_rows.take() else {
 6471            return;
 6472        };
 6473
 6474        window.paint_layer(layout.gutter_hitbox.bounds, |window| {
 6475            for mut blame_element in blamed_display_rows.into_iter() {
 6476                blame_element.paint(window, cx);
 6477            }
 6478        })
 6479    }
 6480
 6481    fn paint_text(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
 6482        window.with_content_mask(
 6483            Some(ContentMask {
 6484                bounds: layout.position_map.text_hitbox.bounds,
 6485            }),
 6486            |window| {
 6487                let editor = self.editor.read(cx);
 6488                if editor.mouse_cursor_hidden {
 6489                    window.set_window_cursor_style(CursorStyle::None);
 6490                } else if let SelectionDragState::ReadyToDrag {
 6491                    mouse_down_time, ..
 6492                } = &editor.selection_drag_state
 6493                {
 6494                    let drag_and_drop_delay = Duration::from_millis(
 6495                        EditorSettings::get_global(cx)
 6496                            .drag_and_drop_selection
 6497                            .delay
 6498                            .0,
 6499                    );
 6500                    if mouse_down_time.elapsed() >= drag_and_drop_delay {
 6501                        window.set_cursor_style(
 6502                            CursorStyle::DragCopy,
 6503                            &layout.position_map.text_hitbox,
 6504                        );
 6505                    }
 6506                } else if matches!(
 6507                    editor.selection_drag_state,
 6508                    SelectionDragState::Dragging { .. }
 6509                ) {
 6510                    window
 6511                        .set_cursor_style(CursorStyle::DragCopy, &layout.position_map.text_hitbox);
 6512                } else if editor
 6513                    .hovered_link_state
 6514                    .as_ref()
 6515                    .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
 6516                {
 6517                    window.set_cursor_style(
 6518                        CursorStyle::PointingHand,
 6519                        &layout.position_map.text_hitbox,
 6520                    );
 6521                } else {
 6522                    window.set_cursor_style(CursorStyle::IBeam, &layout.position_map.text_hitbox);
 6523                };
 6524
 6525                self.paint_lines_background(layout, window, cx);
 6526                let invisible_display_ranges = self.paint_highlights(layout, window, cx);
 6527                self.paint_document_colors(layout, window);
 6528                self.paint_lines(&invisible_display_ranges, layout, window, cx);
 6529                self.paint_redactions(layout, window);
 6530                self.paint_cursors(layout, window, cx);
 6531                self.paint_inline_diagnostics(layout, window, cx);
 6532                self.paint_inline_blame(layout, window, cx);
 6533                self.paint_inline_code_actions(layout, window, cx);
 6534                self.paint_diff_hunk_controls(layout, window, cx);
 6535                window.with_element_namespace("crease_trailers", |window| {
 6536                    for trailer in layout.crease_trailers.iter_mut().flatten() {
 6537                        trailer.element.paint(window, cx);
 6538                    }
 6539                });
 6540            },
 6541        )
 6542    }
 6543
 6544    fn paint_highlights(
 6545        &mut self,
 6546        layout: &mut EditorLayout,
 6547        window: &mut Window,
 6548        cx: &mut App,
 6549    ) -> SmallVec<[Range<DisplayPoint>; 32]> {
 6550        window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
 6551            let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
 6552            let line_end_overshoot = 0.15 * layout.position_map.line_height;
 6553            for (range, color) in &layout.highlighted_ranges {
 6554                self.paint_highlighted_range(
 6555                    range.clone(),
 6556                    true,
 6557                    *color,
 6558                    Pixels::ZERO,
 6559                    line_end_overshoot,
 6560                    layout,
 6561                    window,
 6562                );
 6563            }
 6564
 6565            let corner_radius = if EditorSettings::get_global(cx).rounded_selection {
 6566                0.15 * layout.position_map.line_height
 6567            } else {
 6568                Pixels::ZERO
 6569            };
 6570
 6571            for (player_color, selections) in &layout.selections {
 6572                for selection in selections.iter() {
 6573                    self.paint_highlighted_range(
 6574                        selection.range.clone(),
 6575                        true,
 6576                        player_color.selection,
 6577                        corner_radius,
 6578                        corner_radius * 2.,
 6579                        layout,
 6580                        window,
 6581                    );
 6582
 6583                    if selection.is_local && !selection.range.is_empty() {
 6584                        invisible_display_ranges.push(selection.range.clone());
 6585                    }
 6586                }
 6587            }
 6588            invisible_display_ranges
 6589        })
 6590    }
 6591
 6592    fn paint_lines(
 6593        &mut self,
 6594        invisible_display_ranges: &[Range<DisplayPoint>],
 6595        layout: &mut EditorLayout,
 6596        window: &mut Window,
 6597        cx: &mut App,
 6598    ) {
 6599        let whitespace_setting = self
 6600            .editor
 6601            .read(cx)
 6602            .buffer
 6603            .read(cx)
 6604            .language_settings(cx)
 6605            .show_whitespaces;
 6606
 6607        for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
 6608            let row = DisplayRow(layout.visible_display_row_range.start.0 + ix as u32);
 6609            line_with_invisibles.draw(
 6610                layout,
 6611                row,
 6612                layout.content_origin,
 6613                whitespace_setting,
 6614                invisible_display_ranges,
 6615                window,
 6616                cx,
 6617            )
 6618        }
 6619
 6620        for line_element in &mut layout.line_elements {
 6621            line_element.paint(window, cx);
 6622        }
 6623    }
 6624
 6625    fn paint_sticky_headers(
 6626        &mut self,
 6627        layout: &mut EditorLayout,
 6628        window: &mut Window,
 6629        cx: &mut App,
 6630    ) {
 6631        let Some(mut sticky_headers) = layout.sticky_headers.take() else {
 6632            return;
 6633        };
 6634
 6635        if sticky_headers.lines.is_empty() {
 6636            layout.sticky_headers = Some(sticky_headers);
 6637            return;
 6638        }
 6639
 6640        let whitespace_setting = self
 6641            .editor
 6642            .read(cx)
 6643            .buffer
 6644            .read(cx)
 6645            .language_settings(cx)
 6646            .show_whitespaces;
 6647        sticky_headers.paint(layout, whitespace_setting, window, cx);
 6648
 6649        let sticky_header_hitboxes: Vec<Hitbox> = sticky_headers
 6650            .lines
 6651            .iter()
 6652            .map(|line| line.hitbox.clone())
 6653            .collect();
 6654        let hovered_hitbox = sticky_header_hitboxes
 6655            .iter()
 6656            .find_map(|hitbox| hitbox.is_hovered(window).then_some(hitbox.id));
 6657
 6658        window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, _cx| {
 6659            if !phase.bubble() {
 6660                return;
 6661            }
 6662
 6663            let current_hover = sticky_header_hitboxes
 6664                .iter()
 6665                .find_map(|hitbox| hitbox.is_hovered(window).then_some(hitbox.id));
 6666            if hovered_hitbox != current_hover {
 6667                window.refresh();
 6668            }
 6669        });
 6670
 6671        let position_map = layout.position_map.clone();
 6672
 6673        for (line_index, line) in sticky_headers.lines.iter().enumerate() {
 6674            let editor = self.editor.clone();
 6675            let hitbox = line.hitbox.clone();
 6676            let row = line.row;
 6677            let line_layout = line.line.clone();
 6678            let position_map = position_map.clone();
 6679            window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
 6680                if !phase.bubble() {
 6681                    return;
 6682                }
 6683
 6684                if event.button == MouseButton::Left && hitbox.is_hovered(window) {
 6685                    let point_for_position =
 6686                        position_map.point_for_position_on_line(event.position, row, &line_layout);
 6687
 6688                    editor.update(cx, |editor, cx| {
 6689                        let snapshot = editor.snapshot(window, cx);
 6690                        let anchor = snapshot
 6691                            .display_snapshot
 6692                            .display_point_to_anchor(point_for_position.nearest_valid, Bias::Left);
 6693                        editor.change_selections(
 6694                            SelectionEffects::scroll(Autoscroll::top_relative(line_index)),
 6695                            window,
 6696                            cx,
 6697                            |selections| {
 6698                                selections.clear_disjoint();
 6699                                selections.set_pending_anchor_range(
 6700                                    anchor..anchor,
 6701                                    crate::SelectMode::Character,
 6702                                );
 6703                            },
 6704                        );
 6705                        cx.stop_propagation();
 6706                    });
 6707                }
 6708            });
 6709        }
 6710
 6711        let text_bounds = layout.position_map.text_hitbox.bounds;
 6712        let border_top = text_bounds.top()
 6713            + sticky_headers.lines.last().unwrap().offset
 6714            + layout.position_map.line_height;
 6715        let separator_height = px(1.);
 6716        let border_bounds = Bounds::from_corners(
 6717            point(layout.gutter_hitbox.bounds.left(), border_top),
 6718            point(text_bounds.right(), border_top + separator_height),
 6719        );
 6720        window.paint_quad(fill(border_bounds, cx.theme().colors().border_variant));
 6721
 6722        layout.sticky_headers = Some(sticky_headers);
 6723    }
 6724
 6725    fn paint_lines_background(
 6726        &mut self,
 6727        layout: &mut EditorLayout,
 6728        window: &mut Window,
 6729        cx: &mut App,
 6730    ) {
 6731        for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
 6732            let row = DisplayRow(layout.visible_display_row_range.start.0 + ix as u32);
 6733            line_with_invisibles.draw_background(layout, row, layout.content_origin, window, cx);
 6734        }
 6735    }
 6736
 6737    fn paint_redactions(&mut self, layout: &EditorLayout, window: &mut Window) {
 6738        if layout.redacted_ranges.is_empty() {
 6739            return;
 6740        }
 6741
 6742        let line_end_overshoot = layout.line_end_overshoot();
 6743
 6744        // A softer than perfect black
 6745        let redaction_color = gpui::rgb(0x0e1111);
 6746
 6747        window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
 6748            for range in layout.redacted_ranges.iter() {
 6749                self.paint_highlighted_range(
 6750                    range.clone(),
 6751                    true,
 6752                    redaction_color.into(),
 6753                    Pixels::ZERO,
 6754                    line_end_overshoot,
 6755                    layout,
 6756                    window,
 6757                );
 6758            }
 6759        });
 6760    }
 6761
 6762    fn paint_document_colors(&self, layout: &mut EditorLayout, window: &mut Window) {
 6763        let Some((colors_render_mode, image_colors)) = &layout.document_colors else {
 6764            return;
 6765        };
 6766        if image_colors.is_empty()
 6767            || colors_render_mode == &DocumentColorsRenderMode::None
 6768            || colors_render_mode == &DocumentColorsRenderMode::Inlay
 6769        {
 6770            return;
 6771        }
 6772
 6773        let line_end_overshoot = layout.line_end_overshoot();
 6774
 6775        for (range, color) in image_colors {
 6776            match colors_render_mode {
 6777                DocumentColorsRenderMode::Inlay | DocumentColorsRenderMode::None => return,
 6778                DocumentColorsRenderMode::Background => {
 6779                    self.paint_highlighted_range(
 6780                        range.clone(),
 6781                        true,
 6782                        *color,
 6783                        Pixels::ZERO,
 6784                        line_end_overshoot,
 6785                        layout,
 6786                        window,
 6787                    );
 6788                }
 6789                DocumentColorsRenderMode::Border => {
 6790                    self.paint_highlighted_range(
 6791                        range.clone(),
 6792                        false,
 6793                        *color,
 6794                        Pixels::ZERO,
 6795                        line_end_overshoot,
 6796                        layout,
 6797                        window,
 6798                    );
 6799                }
 6800            }
 6801        }
 6802    }
 6803
 6804    fn paint_cursors(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
 6805        for cursor in &mut layout.visible_cursors {
 6806            cursor.paint(layout.content_origin, window, cx);
 6807        }
 6808    }
 6809
 6810    fn paint_scrollbars(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
 6811        let Some(scrollbars_layout) = layout.scrollbars_layout.take() else {
 6812            return;
 6813        };
 6814        let any_scrollbar_dragged = self.editor.read(cx).scroll_manager.any_scrollbar_dragged();
 6815
 6816        for (scrollbar_layout, axis) in scrollbars_layout.iter_scrollbars() {
 6817            let hitbox = &scrollbar_layout.hitbox;
 6818            if scrollbars_layout.visible {
 6819                let scrollbar_edges = match axis {
 6820                    ScrollbarAxis::Horizontal => Edges {
 6821                        top: Pixels::ZERO,
 6822                        right: Pixels::ZERO,
 6823                        bottom: Pixels::ZERO,
 6824                        left: Pixels::ZERO,
 6825                    },
 6826                    ScrollbarAxis::Vertical => Edges {
 6827                        top: Pixels::ZERO,
 6828                        right: Pixels::ZERO,
 6829                        bottom: Pixels::ZERO,
 6830                        left: ScrollbarLayout::BORDER_WIDTH,
 6831                    },
 6832                };
 6833
 6834                window.paint_layer(hitbox.bounds, |window| {
 6835                    window.paint_quad(quad(
 6836                        hitbox.bounds,
 6837                        Corners::default(),
 6838                        cx.theme().colors().scrollbar_track_background,
 6839                        scrollbar_edges,
 6840                        cx.theme().colors().scrollbar_track_border,
 6841                        BorderStyle::Solid,
 6842                    ));
 6843
 6844                    if axis == ScrollbarAxis::Vertical {
 6845                        let fast_markers =
 6846                            self.collect_fast_scrollbar_markers(layout, scrollbar_layout, cx);
 6847                        // Refresh slow scrollbar markers in the background. Below, we
 6848                        // paint whatever markers have already been computed.
 6849                        self.refresh_slow_scrollbar_markers(layout, scrollbar_layout, window, cx);
 6850
 6851                        let markers = self.editor.read(cx).scrollbar_marker_state.markers.clone();
 6852                        for marker in markers.iter().chain(&fast_markers) {
 6853                            let mut marker = marker.clone();
 6854                            marker.bounds.origin += hitbox.origin;
 6855                            window.paint_quad(marker);
 6856                        }
 6857                    }
 6858
 6859                    if let Some(thumb_bounds) = scrollbar_layout.thumb_bounds {
 6860                        let scrollbar_thumb_color = match scrollbar_layout.thumb_state {
 6861                            ScrollbarThumbState::Dragging => {
 6862                                cx.theme().colors().scrollbar_thumb_active_background
 6863                            }
 6864                            ScrollbarThumbState::Hovered => {
 6865                                cx.theme().colors().scrollbar_thumb_hover_background
 6866                            }
 6867                            ScrollbarThumbState::Idle => {
 6868                                cx.theme().colors().scrollbar_thumb_background
 6869                            }
 6870                        };
 6871                        window.paint_quad(quad(
 6872                            thumb_bounds,
 6873                            Corners::default(),
 6874                            scrollbar_thumb_color,
 6875                            scrollbar_edges,
 6876                            cx.theme().colors().scrollbar_thumb_border,
 6877                            BorderStyle::Solid,
 6878                        ));
 6879
 6880                        if any_scrollbar_dragged {
 6881                            window.set_window_cursor_style(CursorStyle::Arrow);
 6882                        } else {
 6883                            window.set_cursor_style(CursorStyle::Arrow, hitbox);
 6884                        }
 6885                    }
 6886                })
 6887            }
 6888        }
 6889
 6890        window.on_mouse_event({
 6891            let editor = self.editor.clone();
 6892            let scrollbars_layout = scrollbars_layout.clone();
 6893
 6894            let mut mouse_position = window.mouse_position();
 6895            move |event: &MouseMoveEvent, phase, window, cx| {
 6896                if phase == DispatchPhase::Capture {
 6897                    return;
 6898                }
 6899
 6900                editor.update(cx, |editor, cx| {
 6901                    if let Some((scrollbar_layout, axis)) = event
 6902                        .pressed_button
 6903                        .filter(|button| *button == MouseButton::Left)
 6904                        .and(editor.scroll_manager.dragging_scrollbar_axis())
 6905                        .and_then(|axis| {
 6906                            scrollbars_layout
 6907                                .iter_scrollbars()
 6908                                .find(|(_, a)| *a == axis)
 6909                        })
 6910                    {
 6911                        let ScrollbarLayout {
 6912                            hitbox,
 6913                            text_unit_size,
 6914                            ..
 6915                        } = scrollbar_layout;
 6916
 6917                        let old_position = mouse_position.along(axis);
 6918                        let new_position = event.position.along(axis);
 6919                        if (hitbox.origin.along(axis)..hitbox.bottom_right().along(axis))
 6920                            .contains(&old_position)
 6921                        {
 6922                            let position = editor.scroll_position(cx).apply_along(axis, |p| {
 6923                                (p + ScrollOffset::from(
 6924                                    (new_position - old_position) / *text_unit_size,
 6925                                ))
 6926                                .max(0.)
 6927                            });
 6928                            editor.set_scroll_position(position, window, cx);
 6929                        }
 6930
 6931                        editor.scroll_manager.show_scrollbars(window, cx);
 6932                        cx.stop_propagation();
 6933                    } else if let Some((layout, axis)) = scrollbars_layout
 6934                        .get_hovered_axis(window)
 6935                        .filter(|_| !event.dragging())
 6936                    {
 6937                        if layout.thumb_hovered(&event.position) {
 6938                            editor
 6939                                .scroll_manager
 6940                                .set_hovered_scroll_thumb_axis(axis, cx);
 6941                        } else {
 6942                            editor.scroll_manager.reset_scrollbar_state(cx);
 6943                        }
 6944
 6945                        editor.scroll_manager.show_scrollbars(window, cx);
 6946                    } else {
 6947                        editor.scroll_manager.reset_scrollbar_state(cx);
 6948                    }
 6949
 6950                    mouse_position = event.position;
 6951                })
 6952            }
 6953        });
 6954
 6955        if any_scrollbar_dragged {
 6956            window.on_mouse_event({
 6957                let editor = self.editor.clone();
 6958                move |_: &MouseUpEvent, phase, window, cx| {
 6959                    if phase == DispatchPhase::Capture {
 6960                        return;
 6961                    }
 6962
 6963                    editor.update(cx, |editor, cx| {
 6964                        if let Some((_, axis)) = scrollbars_layout.get_hovered_axis(window) {
 6965                            editor
 6966                                .scroll_manager
 6967                                .set_hovered_scroll_thumb_axis(axis, cx);
 6968                        } else {
 6969                            editor.scroll_manager.reset_scrollbar_state(cx);
 6970                        }
 6971                        cx.stop_propagation();
 6972                    });
 6973                }
 6974            });
 6975        } else {
 6976            window.on_mouse_event({
 6977                let editor = self.editor.clone();
 6978
 6979                move |event: &MouseDownEvent, phase, window, cx| {
 6980                    if phase == DispatchPhase::Capture {
 6981                        return;
 6982                    }
 6983                    let Some((scrollbar_layout, axis)) = scrollbars_layout.get_hovered_axis(window)
 6984                    else {
 6985                        return;
 6986                    };
 6987
 6988                    let ScrollbarLayout {
 6989                        hitbox,
 6990                        visible_range,
 6991                        text_unit_size,
 6992                        thumb_bounds,
 6993                        ..
 6994                    } = scrollbar_layout;
 6995
 6996                    let Some(thumb_bounds) = thumb_bounds else {
 6997                        return;
 6998                    };
 6999
 7000                    editor.update(cx, |editor, cx| {
 7001                        editor
 7002                            .scroll_manager
 7003                            .set_dragged_scroll_thumb_axis(axis, cx);
 7004
 7005                        let event_position = event.position.along(axis);
 7006
 7007                        if event_position < thumb_bounds.origin.along(axis)
 7008                            || thumb_bounds.bottom_right().along(axis) < event_position
 7009                        {
 7010                            let center_position = ((event_position - hitbox.origin.along(axis))
 7011                                / *text_unit_size)
 7012                                .round() as u32;
 7013                            let start_position = center_position.saturating_sub(
 7014                                (visible_range.end - visible_range.start) as u32 / 2,
 7015                            );
 7016
 7017                            let position = editor
 7018                                .scroll_position(cx)
 7019                                .apply_along(axis, |_| start_position as ScrollOffset);
 7020
 7021                            editor.set_scroll_position(position, window, cx);
 7022                        } else {
 7023                            editor.scroll_manager.show_scrollbars(window, cx);
 7024                        }
 7025
 7026                        cx.stop_propagation();
 7027                    });
 7028                }
 7029            });
 7030        }
 7031    }
 7032
 7033    fn collect_fast_scrollbar_markers(
 7034        &self,
 7035        layout: &EditorLayout,
 7036        scrollbar_layout: &ScrollbarLayout,
 7037        cx: &mut App,
 7038    ) -> Vec<PaintQuad> {
 7039        const LIMIT: usize = 100;
 7040        if !EditorSettings::get_global(cx).scrollbar.cursors || layout.cursors.len() > LIMIT {
 7041            return vec![];
 7042        }
 7043        let cursor_ranges = layout
 7044            .cursors
 7045            .iter()
 7046            .map(|(point, color)| ColoredRange {
 7047                start: point.row(),
 7048                end: point.row(),
 7049                color: *color,
 7050            })
 7051            .collect_vec();
 7052        scrollbar_layout.marker_quads_for_ranges(cursor_ranges, None)
 7053    }
 7054
 7055    fn refresh_slow_scrollbar_markers(
 7056        &self,
 7057        layout: &EditorLayout,
 7058        scrollbar_layout: &ScrollbarLayout,
 7059        window: &mut Window,
 7060        cx: &mut App,
 7061    ) {
 7062        self.editor.update(cx, |editor, cx| {
 7063            if editor.buffer_kind(cx) != ItemBufferKind::Singleton
 7064                || !editor
 7065                    .scrollbar_marker_state
 7066                    .should_refresh(scrollbar_layout.hitbox.size)
 7067            {
 7068                return;
 7069            }
 7070
 7071            let scrollbar_layout = scrollbar_layout.clone();
 7072            let background_highlights = editor.background_highlights.clone();
 7073            let snapshot = layout.position_map.snapshot.clone();
 7074            let theme = cx.theme().clone();
 7075            let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
 7076
 7077            editor.scrollbar_marker_state.dirty = false;
 7078            editor.scrollbar_marker_state.pending_refresh =
 7079                Some(cx.spawn_in(window, async move |editor, cx| {
 7080                    let scrollbar_size = scrollbar_layout.hitbox.size;
 7081                    let scrollbar_markers = cx
 7082                        .background_spawn(async move {
 7083                            let max_point = snapshot.display_snapshot.buffer_snapshot().max_point();
 7084                            let mut marker_quads = Vec::new();
 7085                            if scrollbar_settings.git_diff {
 7086                                let marker_row_ranges =
 7087                                    snapshot.buffer_snapshot().diff_hunks().map(|hunk| {
 7088                                        let start_display_row =
 7089                                            MultiBufferPoint::new(hunk.row_range.start.0, 0)
 7090                                                .to_display_point(&snapshot.display_snapshot)
 7091                                                .row();
 7092                                        let mut end_display_row =
 7093                                            MultiBufferPoint::new(hunk.row_range.end.0, 0)
 7094                                                .to_display_point(&snapshot.display_snapshot)
 7095                                                .row();
 7096                                        if end_display_row != start_display_row {
 7097                                            end_display_row.0 -= 1;
 7098                                        }
 7099                                        let color = match &hunk.status().kind {
 7100                                            DiffHunkStatusKind::Added => {
 7101                                                theme.colors().version_control_added
 7102                                            }
 7103                                            DiffHunkStatusKind::Modified => {
 7104                                                theme.colors().version_control_modified
 7105                                            }
 7106                                            DiffHunkStatusKind::Deleted => {
 7107                                                theme.colors().version_control_deleted
 7108                                            }
 7109                                        };
 7110                                        ColoredRange {
 7111                                            start: start_display_row,
 7112                                            end: end_display_row,
 7113                                            color,
 7114                                        }
 7115                                    });
 7116
 7117                                marker_quads.extend(
 7118                                    scrollbar_layout
 7119                                        .marker_quads_for_ranges(marker_row_ranges, Some(0)),
 7120                                );
 7121                            }
 7122
 7123                            for (background_highlight_id, (_, background_ranges)) in
 7124                                background_highlights.iter()
 7125                            {
 7126                                let is_search_highlights = *background_highlight_id
 7127                                    == HighlightKey::BufferSearchHighlights;
 7128                                let is_text_highlights =
 7129                                    *background_highlight_id == HighlightKey::SelectedTextHighlight;
 7130                                let is_symbol_occurrences = *background_highlight_id
 7131                                    == HighlightKey::DocumentHighlightRead
 7132                                    || *background_highlight_id
 7133                                        == HighlightKey::DocumentHighlightWrite;
 7134                                if (is_search_highlights && scrollbar_settings.search_results)
 7135                                    || (is_text_highlights && scrollbar_settings.selected_text)
 7136                                    || (is_symbol_occurrences && scrollbar_settings.selected_symbol)
 7137                                {
 7138                                    let mut color = theme.status().info;
 7139                                    if is_symbol_occurrences {
 7140                                        color.fade_out(0.5);
 7141                                    }
 7142                                    let marker_row_ranges = background_ranges.iter().map(|range| {
 7143                                        let display_start = range
 7144                                            .start
 7145                                            .to_display_point(&snapshot.display_snapshot);
 7146                                        let display_end =
 7147                                            range.end.to_display_point(&snapshot.display_snapshot);
 7148                                        ColoredRange {
 7149                                            start: display_start.row(),
 7150                                            end: display_end.row(),
 7151                                            color,
 7152                                        }
 7153                                    });
 7154                                    marker_quads.extend(
 7155                                        scrollbar_layout
 7156                                            .marker_quads_for_ranges(marker_row_ranges, Some(1)),
 7157                                    );
 7158                                }
 7159                            }
 7160
 7161                            if scrollbar_settings.diagnostics != ScrollbarDiagnostics::None {
 7162                                let diagnostics = snapshot
 7163                                    .buffer_snapshot()
 7164                                    .diagnostics_in_range::<Point>(Point::zero()..max_point)
 7165                                    // Don't show diagnostics the user doesn't care about
 7166                                    .filter(|diagnostic| {
 7167                                        match (
 7168                                            scrollbar_settings.diagnostics,
 7169                                            diagnostic.diagnostic.severity,
 7170                                        ) {
 7171                                            (ScrollbarDiagnostics::All, _) => true,
 7172                                            (
 7173                                                ScrollbarDiagnostics::Error,
 7174                                                lsp::DiagnosticSeverity::ERROR,
 7175                                            ) => true,
 7176                                            (
 7177                                                ScrollbarDiagnostics::Warning,
 7178                                                lsp::DiagnosticSeverity::ERROR
 7179                                                | lsp::DiagnosticSeverity::WARNING,
 7180                                            ) => true,
 7181                                            (
 7182                                                ScrollbarDiagnostics::Information,
 7183                                                lsp::DiagnosticSeverity::ERROR
 7184                                                | lsp::DiagnosticSeverity::WARNING
 7185                                                | lsp::DiagnosticSeverity::INFORMATION,
 7186                                            ) => true,
 7187                                            (_, _) => false,
 7188                                        }
 7189                                    })
 7190                                    // We want to sort by severity, in order to paint the most severe diagnostics last.
 7191                                    .sorted_by_key(|diagnostic| {
 7192                                        std::cmp::Reverse(diagnostic.diagnostic.severity)
 7193                                    });
 7194
 7195                                let marker_row_ranges = diagnostics.into_iter().map(|diagnostic| {
 7196                                    let start_display = diagnostic
 7197                                        .range
 7198                                        .start
 7199                                        .to_display_point(&snapshot.display_snapshot);
 7200                                    let end_display = diagnostic
 7201                                        .range
 7202                                        .end
 7203                                        .to_display_point(&snapshot.display_snapshot);
 7204                                    let color = match diagnostic.diagnostic.severity {
 7205                                        lsp::DiagnosticSeverity::ERROR => theme.status().error,
 7206                                        lsp::DiagnosticSeverity::WARNING => theme.status().warning,
 7207                                        lsp::DiagnosticSeverity::INFORMATION => theme.status().info,
 7208                                        _ => theme.status().hint,
 7209                                    };
 7210                                    ColoredRange {
 7211                                        start: start_display.row(),
 7212                                        end: end_display.row(),
 7213                                        color,
 7214                                    }
 7215                                });
 7216                                marker_quads.extend(
 7217                                    scrollbar_layout
 7218                                        .marker_quads_for_ranges(marker_row_ranges, Some(2)),
 7219                                );
 7220                            }
 7221
 7222                            Arc::from(marker_quads)
 7223                        })
 7224                        .await;
 7225
 7226                    editor.update(cx, |editor, cx| {
 7227                        editor.scrollbar_marker_state.markers = scrollbar_markers;
 7228                        editor.scrollbar_marker_state.scrollbar_size = scrollbar_size;
 7229                        editor.scrollbar_marker_state.pending_refresh = None;
 7230                        cx.notify();
 7231                    })?;
 7232
 7233                    Ok(())
 7234                }));
 7235        });
 7236    }
 7237
 7238    fn paint_highlighted_range(
 7239        &self,
 7240        range: Range<DisplayPoint>,
 7241        fill: bool,
 7242        color: Hsla,
 7243        corner_radius: Pixels,
 7244        line_end_overshoot: Pixels,
 7245        layout: &EditorLayout,
 7246        window: &mut Window,
 7247    ) {
 7248        let start_row = layout.visible_display_row_range.start;
 7249        let end_row = layout.visible_display_row_range.end;
 7250        if range.start != range.end {
 7251            let row_range = if range.end.column() == 0 {
 7252                cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
 7253            } else {
 7254                cmp::max(range.start.row(), start_row)
 7255                    ..cmp::min(range.end.row().next_row(), end_row)
 7256            };
 7257
 7258            let highlighted_range = HighlightedRange {
 7259                color,
 7260                line_height: layout.position_map.line_height,
 7261                corner_radius,
 7262                start_y: layout.content_origin.y
 7263                    + Pixels::from(
 7264                        (row_range.start.as_f64() - layout.position_map.scroll_position.y)
 7265                            * ScrollOffset::from(layout.position_map.line_height),
 7266                    ),
 7267                lines: row_range
 7268                    .iter_rows()
 7269                    .map(|row| {
 7270                        let line_layout =
 7271                            &layout.position_map.line_layouts[row.minus(start_row) as usize];
 7272                        let alignment_offset =
 7273                            line_layout.alignment_offset(layout.text_align, layout.content_width);
 7274                        HighlightedRangeLine {
 7275                            start_x: if row == range.start.row() {
 7276                                layout.content_origin.x
 7277                                    + Pixels::from(
 7278                                        ScrollPixelOffset::from(
 7279                                            line_layout.x_for_index(range.start.column() as usize)
 7280                                                + alignment_offset,
 7281                                        ) - layout.position_map.scroll_pixel_position.x,
 7282                                    )
 7283                            } else {
 7284                                layout.content_origin.x + alignment_offset
 7285                                    - Pixels::from(layout.position_map.scroll_pixel_position.x)
 7286                            },
 7287                            end_x: if row == range.end.row() {
 7288                                layout.content_origin.x
 7289                                    + Pixels::from(
 7290                                        ScrollPixelOffset::from(
 7291                                            line_layout.x_for_index(range.end.column() as usize)
 7292                                                + alignment_offset,
 7293                                        ) - layout.position_map.scroll_pixel_position.x,
 7294                                    )
 7295                            } else {
 7296                                Pixels::from(
 7297                                    ScrollPixelOffset::from(
 7298                                        layout.content_origin.x
 7299                                            + line_layout.width
 7300                                            + alignment_offset
 7301                                            + line_end_overshoot,
 7302                                    ) - layout.position_map.scroll_pixel_position.x,
 7303                                )
 7304                            },
 7305                        }
 7306                    })
 7307                    .collect(),
 7308            };
 7309
 7310            highlighted_range.paint(fill, layout.position_map.text_hitbox.bounds, window);
 7311        }
 7312    }
 7313
 7314    fn paint_inline_diagnostics(
 7315        &mut self,
 7316        layout: &mut EditorLayout,
 7317        window: &mut Window,
 7318        cx: &mut App,
 7319    ) {
 7320        for mut inline_diagnostic in layout.inline_diagnostics.drain() {
 7321            inline_diagnostic.1.paint(window, cx);
 7322        }
 7323    }
 7324
 7325    fn paint_inline_blame(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
 7326        if let Some(mut blame_layout) = layout.inline_blame_layout.take() {
 7327            window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
 7328                blame_layout.element.paint(window, cx);
 7329            })
 7330        }
 7331    }
 7332
 7333    fn paint_inline_code_actions(
 7334        &mut self,
 7335        layout: &mut EditorLayout,
 7336        window: &mut Window,
 7337        cx: &mut App,
 7338    ) {
 7339        if let Some(mut inline_code_actions) = layout.inline_code_actions.take() {
 7340            window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
 7341                inline_code_actions.paint(window, cx);
 7342            })
 7343        }
 7344    }
 7345
 7346    fn paint_diff_hunk_controls(
 7347        &mut self,
 7348        layout: &mut EditorLayout,
 7349        window: &mut Window,
 7350        cx: &mut App,
 7351    ) {
 7352        for mut diff_hunk_control in layout.diff_hunk_controls.drain(..) {
 7353            diff_hunk_control.paint(window, cx);
 7354        }
 7355    }
 7356
 7357    fn paint_minimap(&self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
 7358        if let Some(mut layout) = layout.minimap.take() {
 7359            let minimap_hitbox = layout.thumb_layout.hitbox.clone();
 7360            let dragging_minimap = self.editor.read(cx).scroll_manager.is_dragging_minimap();
 7361
 7362            window.paint_layer(layout.thumb_layout.hitbox.bounds, |window| {
 7363                window.with_element_namespace("minimap", |window| {
 7364                    layout.minimap.paint(window, cx);
 7365                    if let Some(thumb_bounds) = layout.thumb_layout.thumb_bounds {
 7366                        let minimap_thumb_color = match layout.thumb_layout.thumb_state {
 7367                            ScrollbarThumbState::Idle => {
 7368                                cx.theme().colors().minimap_thumb_background
 7369                            }
 7370                            ScrollbarThumbState::Hovered => {
 7371                                cx.theme().colors().minimap_thumb_hover_background
 7372                            }
 7373                            ScrollbarThumbState::Dragging => {
 7374                                cx.theme().colors().minimap_thumb_active_background
 7375                            }
 7376                        };
 7377                        let minimap_thumb_border = match layout.thumb_border_style {
 7378                            MinimapThumbBorder::Full => Edges::all(ScrollbarLayout::BORDER_WIDTH),
 7379                            MinimapThumbBorder::LeftOnly => Edges {
 7380                                left: ScrollbarLayout::BORDER_WIDTH,
 7381                                ..Default::default()
 7382                            },
 7383                            MinimapThumbBorder::LeftOpen => Edges {
 7384                                right: ScrollbarLayout::BORDER_WIDTH,
 7385                                top: ScrollbarLayout::BORDER_WIDTH,
 7386                                bottom: ScrollbarLayout::BORDER_WIDTH,
 7387                                ..Default::default()
 7388                            },
 7389                            MinimapThumbBorder::RightOpen => Edges {
 7390                                left: ScrollbarLayout::BORDER_WIDTH,
 7391                                top: ScrollbarLayout::BORDER_WIDTH,
 7392                                bottom: ScrollbarLayout::BORDER_WIDTH,
 7393                                ..Default::default()
 7394                            },
 7395                            MinimapThumbBorder::None => Default::default(),
 7396                        };
 7397
 7398                        window.paint_layer(minimap_hitbox.bounds, |window| {
 7399                            window.paint_quad(quad(
 7400                                thumb_bounds,
 7401                                Corners::default(),
 7402                                minimap_thumb_color,
 7403                                minimap_thumb_border,
 7404                                cx.theme().colors().minimap_thumb_border,
 7405                                BorderStyle::Solid,
 7406                            ));
 7407                        });
 7408                    }
 7409                });
 7410            });
 7411
 7412            if dragging_minimap {
 7413                window.set_window_cursor_style(CursorStyle::Arrow);
 7414            } else {
 7415                window.set_cursor_style(CursorStyle::Arrow, &minimap_hitbox);
 7416            }
 7417
 7418            let minimap_axis = ScrollbarAxis::Vertical;
 7419            let pixels_per_line = Pixels::from(
 7420                ScrollPixelOffset::from(minimap_hitbox.size.height) / layout.max_scroll_top,
 7421            )
 7422            .min(layout.minimap_line_height);
 7423
 7424            let mut mouse_position = window.mouse_position();
 7425
 7426            window.on_mouse_event({
 7427                let editor = self.editor.clone();
 7428
 7429                let minimap_hitbox = minimap_hitbox.clone();
 7430
 7431                move |event: &MouseMoveEvent, phase, window, cx| {
 7432                    if phase == DispatchPhase::Capture {
 7433                        return;
 7434                    }
 7435
 7436                    editor.update(cx, |editor, cx| {
 7437                        if event.pressed_button == Some(MouseButton::Left)
 7438                            && editor.scroll_manager.is_dragging_minimap()
 7439                        {
 7440                            let old_position = mouse_position.along(minimap_axis);
 7441                            let new_position = event.position.along(minimap_axis);
 7442                            if (minimap_hitbox.origin.along(minimap_axis)
 7443                                ..minimap_hitbox.bottom_right().along(minimap_axis))
 7444                                .contains(&old_position)
 7445                            {
 7446                                let position =
 7447                                    editor.scroll_position(cx).apply_along(minimap_axis, |p| {
 7448                                        (p + ScrollPixelOffset::from(
 7449                                            (new_position - old_position) / pixels_per_line,
 7450                                        ))
 7451                                        .max(0.)
 7452                                    });
 7453
 7454                                editor.set_scroll_position(position, window, cx);
 7455                            }
 7456                            cx.stop_propagation();
 7457                        } else if minimap_hitbox.is_hovered(window) {
 7458                            editor.scroll_manager.set_is_hovering_minimap_thumb(
 7459                                !event.dragging()
 7460                                    && layout
 7461                                        .thumb_layout
 7462                                        .thumb_bounds
 7463                                        .is_some_and(|bounds| bounds.contains(&event.position)),
 7464                                cx,
 7465                            );
 7466
 7467                            // Stop hover events from propagating to the
 7468                            // underlying editor if the minimap hitbox is hovered
 7469                            if !event.dragging() {
 7470                                cx.stop_propagation();
 7471                            }
 7472                        } else {
 7473                            editor.scroll_manager.hide_minimap_thumb(cx);
 7474                        }
 7475                        mouse_position = event.position;
 7476                    });
 7477                }
 7478            });
 7479
 7480            if dragging_minimap {
 7481                window.on_mouse_event({
 7482                    let editor = self.editor.clone();
 7483                    move |event: &MouseUpEvent, phase, window, cx| {
 7484                        if phase == DispatchPhase::Capture {
 7485                            return;
 7486                        }
 7487
 7488                        editor.update(cx, |editor, cx| {
 7489                            if minimap_hitbox.is_hovered(window) {
 7490                                editor.scroll_manager.set_is_hovering_minimap_thumb(
 7491                                    layout
 7492                                        .thumb_layout
 7493                                        .thumb_bounds
 7494                                        .is_some_and(|bounds| bounds.contains(&event.position)),
 7495                                    cx,
 7496                                );
 7497                            } else {
 7498                                editor.scroll_manager.hide_minimap_thumb(cx);
 7499                            }
 7500                            cx.stop_propagation();
 7501                        });
 7502                    }
 7503                });
 7504            } else {
 7505                window.on_mouse_event({
 7506                    let editor = self.editor.clone();
 7507
 7508                    move |event: &MouseDownEvent, phase, window, cx| {
 7509                        if phase == DispatchPhase::Capture || !minimap_hitbox.is_hovered(window) {
 7510                            return;
 7511                        }
 7512
 7513                        let event_position = event.position;
 7514
 7515                        let Some(thumb_bounds) = layout.thumb_layout.thumb_bounds else {
 7516                            return;
 7517                        };
 7518
 7519                        editor.update(cx, |editor, cx| {
 7520                            if !thumb_bounds.contains(&event_position) {
 7521                                let click_position =
 7522                                    event_position.relative_to(&minimap_hitbox.origin).y;
 7523
 7524                                let top_position = (click_position
 7525                                    - thumb_bounds.size.along(minimap_axis) / 2.0)
 7526                                    .max(Pixels::ZERO);
 7527
 7528                                let scroll_offset = (layout.minimap_scroll_top
 7529                                    + ScrollPixelOffset::from(
 7530                                        top_position / layout.minimap_line_height,
 7531                                    ))
 7532                                .min(layout.max_scroll_top);
 7533
 7534                                let scroll_position = editor
 7535                                    .scroll_position(cx)
 7536                                    .apply_along(minimap_axis, |_| scroll_offset);
 7537                                editor.set_scroll_position(scroll_position, window, cx);
 7538                            }
 7539
 7540                            editor.scroll_manager.set_is_dragging_minimap(cx);
 7541                            cx.stop_propagation();
 7542                        });
 7543                    }
 7544                });
 7545            }
 7546        }
 7547    }
 7548
 7549    fn paint_spacer_blocks(
 7550        &mut self,
 7551        layout: &mut EditorLayout,
 7552        window: &mut Window,
 7553        cx: &mut App,
 7554    ) {
 7555        for mut block in layout.spacer_blocks.drain(..) {
 7556            let mut bounds = layout.hitbox.bounds;
 7557            bounds.origin.x += layout.gutter_hitbox.bounds.size.width;
 7558            window.with_content_mask(Some(ContentMask { bounds }), |window| {
 7559                block.element.paint(window, cx);
 7560            })
 7561        }
 7562    }
 7563
 7564    fn paint_non_spacer_blocks(
 7565        &mut self,
 7566        layout: &mut EditorLayout,
 7567        window: &mut Window,
 7568        cx: &mut App,
 7569    ) {
 7570        for mut block in layout.blocks.drain(..) {
 7571            if block.overlaps_gutter {
 7572                block.element.paint(window, cx);
 7573            } else {
 7574                let mut bounds = layout.hitbox.bounds;
 7575                bounds.origin.x += layout.gutter_hitbox.bounds.size.width;
 7576                window.with_content_mask(Some(ContentMask { bounds }), |window| {
 7577                    block.element.paint(window, cx);
 7578                })
 7579            }
 7580        }
 7581    }
 7582
 7583    fn paint_edit_prediction_popover(
 7584        &mut self,
 7585        layout: &mut EditorLayout,
 7586        window: &mut Window,
 7587        cx: &mut App,
 7588    ) {
 7589        if let Some(edit_prediction_popover) = layout.edit_prediction_popover.as_mut() {
 7590            edit_prediction_popover.paint(window, cx);
 7591        }
 7592    }
 7593
 7594    fn paint_mouse_context_menu(
 7595        &mut self,
 7596        layout: &mut EditorLayout,
 7597        window: &mut Window,
 7598        cx: &mut App,
 7599    ) {
 7600        if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() {
 7601            mouse_context_menu.paint(window, cx);
 7602        }
 7603    }
 7604
 7605    fn paint_scroll_wheel_listener(
 7606        &mut self,
 7607        layout: &EditorLayout,
 7608        window: &mut Window,
 7609        cx: &mut App,
 7610    ) {
 7611        window.on_mouse_event({
 7612            let position_map = layout.position_map.clone();
 7613            let editor = self.editor.clone();
 7614            let hitbox = layout.hitbox.clone();
 7615            let mut delta = ScrollDelta::default();
 7616
 7617            // Set a minimum scroll_sensitivity of 0.01 to make sure the user doesn't
 7618            // accidentally turn off their scrolling.
 7619            let base_scroll_sensitivity =
 7620                EditorSettings::get_global(cx).scroll_sensitivity.max(0.01);
 7621
 7622            // Use a minimum fast_scroll_sensitivity for same reason above
 7623            let fast_scroll_sensitivity = EditorSettings::get_global(cx)
 7624                .fast_scroll_sensitivity
 7625                .max(0.01);
 7626
 7627            move |event: &ScrollWheelEvent, phase, window, cx| {
 7628                if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
 7629                    delta = delta.coalesce(event.delta);
 7630
 7631                    if event.modifiers.secondary()
 7632                        && editor.read(cx).enable_mouse_wheel_zoom
 7633                        && EditorSettings::get_global(cx).mouse_wheel_zoom
 7634                    {
 7635                        let delta_y = match event.delta {
 7636                            ScrollDelta::Pixels(pixels) => pixels.y.into(),
 7637                            ScrollDelta::Lines(lines) => lines.y,
 7638                        };
 7639
 7640                        if delta_y > 0.0 {
 7641                            theme_settings::increase_buffer_font_size(cx);
 7642                        } else if delta_y < 0.0 {
 7643                            theme_settings::decrease_buffer_font_size(cx);
 7644                        }
 7645
 7646                        cx.stop_propagation();
 7647                    } else {
 7648                        let scroll_sensitivity = {
 7649                            if event.modifiers.alt {
 7650                                fast_scroll_sensitivity
 7651                            } else {
 7652                                base_scroll_sensitivity
 7653                            }
 7654                        };
 7655
 7656                        editor.update(cx, |editor, cx| {
 7657                            let line_height = position_map.line_height;
 7658                            let glyph_width = position_map.em_layout_width;
 7659                            let (delta, axis) = match delta {
 7660                                gpui::ScrollDelta::Pixels(mut pixels) => {
 7661                                    //Trackpad
 7662                                    let axis =
 7663                                        position_map.snapshot.ongoing_scroll.filter(&mut pixels);
 7664                                    (pixels, axis)
 7665                                }
 7666
 7667                                gpui::ScrollDelta::Lines(lines) => {
 7668                                    //Not trackpad
 7669                                    let pixels =
 7670                                        point(lines.x * glyph_width, lines.y * line_height);
 7671                                    (pixels, None)
 7672                                }
 7673                            };
 7674
 7675                            let current_scroll_position = position_map.snapshot.scroll_position();
 7676                            let x = (current_scroll_position.x
 7677                                * ScrollPixelOffset::from(glyph_width)
 7678                                - ScrollPixelOffset::from(delta.x * scroll_sensitivity))
 7679                                / ScrollPixelOffset::from(glyph_width);
 7680                            let y = (current_scroll_position.y
 7681                                * ScrollPixelOffset::from(line_height)
 7682                                - ScrollPixelOffset::from(delta.y * scroll_sensitivity))
 7683                                / ScrollPixelOffset::from(line_height);
 7684                            let mut scroll_position =
 7685                                point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
 7686                            let forbid_vertical_scroll =
 7687                                editor.scroll_manager.forbid_vertical_scroll();
 7688                            if forbid_vertical_scroll {
 7689                                scroll_position.y = current_scroll_position.y;
 7690                            }
 7691
 7692                            if scroll_position != current_scroll_position {
 7693                                editor.scroll(scroll_position, axis, window, cx);
 7694                                cx.stop_propagation();
 7695                            } else if y < 0. {
 7696                                // Due to clamping, we may fail to detect cases of overscroll to the top;
 7697                                // We want the scroll manager to get an update in such cases and detect the change of direction
 7698                                // on the next frame.
 7699                                cx.notify();
 7700                            }
 7701                        });
 7702                    }
 7703                }
 7704            }
 7705        });
 7706    }
 7707
 7708    fn paint_mouse_listeners(&mut self, layout: &EditorLayout, window: &mut Window, cx: &mut App) {
 7709        if layout.mode.is_minimap() {
 7710            return;
 7711        }
 7712
 7713        self.paint_scroll_wheel_listener(layout, window, cx);
 7714
 7715        window.on_mouse_event({
 7716            let position_map = layout.position_map.clone();
 7717            let editor = self.editor.clone();
 7718            let line_numbers = layout.line_numbers.clone();
 7719
 7720            move |event: &MouseDownEvent, phase, window, cx| {
 7721                if phase == DispatchPhase::Bubble {
 7722                    match event.button {
 7723                        MouseButton::Left => editor.update(cx, |editor, cx| {
 7724                            let pending_mouse_down = editor
 7725                                .pending_mouse_down
 7726                                .get_or_insert_with(Default::default)
 7727                                .clone();
 7728
 7729                            *pending_mouse_down.borrow_mut() = Some(event.clone());
 7730
 7731                            Self::mouse_left_down(
 7732                                editor,
 7733                                event,
 7734                                &position_map,
 7735                                line_numbers.as_ref(),
 7736                                window,
 7737                                cx,
 7738                            );
 7739                        }),
 7740                        MouseButton::Right => editor.update(cx, |editor, cx| {
 7741                            Self::mouse_right_down(editor, event, &position_map, window, cx);
 7742                        }),
 7743                        MouseButton::Middle => editor.update(cx, |editor, cx| {
 7744                            Self::mouse_middle_down(editor, event, &position_map, window, cx);
 7745                        }),
 7746                        _ => {}
 7747                    };
 7748                }
 7749            }
 7750        });
 7751
 7752        window.on_mouse_event({
 7753            let editor = self.editor.clone();
 7754            let position_map = layout.position_map.clone();
 7755
 7756            move |event: &MouseUpEvent, phase, window, cx| {
 7757                if phase == DispatchPhase::Bubble {
 7758                    editor.update(cx, |editor, cx| {
 7759                        Self::mouse_up(editor, event, &position_map, window, cx)
 7760                    });
 7761                }
 7762            }
 7763        });
 7764
 7765        window.on_mouse_event({
 7766            let editor = self.editor.clone();
 7767            let position_map = layout.position_map.clone();
 7768            let mut captured_mouse_down = None;
 7769
 7770            move |event: &MouseUpEvent, phase, window, cx| match phase {
 7771                // Clear the pending mouse down during the capture phase,
 7772                // so that it happens even if another event handler stops
 7773                // propagation.
 7774                DispatchPhase::Capture => editor.update(cx, |editor, _cx| {
 7775                    let pending_mouse_down = editor
 7776                        .pending_mouse_down
 7777                        .get_or_insert_with(Default::default)
 7778                        .clone();
 7779
 7780                    let mut pending_mouse_down = pending_mouse_down.borrow_mut();
 7781                    if pending_mouse_down.is_some() && position_map.text_hitbox.is_hovered(window) {
 7782                        captured_mouse_down = pending_mouse_down.take();
 7783                        window.refresh();
 7784                    }
 7785                }),
 7786                // Fire click handlers during the bubble phase.
 7787                DispatchPhase::Bubble => editor.update(cx, |editor, cx| {
 7788                    if let Some(mouse_down) = captured_mouse_down.take() {
 7789                        let event = ClickEvent::Mouse(MouseClickEvent {
 7790                            down: mouse_down,
 7791                            up: event.clone(),
 7792                        });
 7793                        Self::click(editor, &event, &position_map, window, cx);
 7794                    }
 7795                }),
 7796            }
 7797        });
 7798
 7799        window.on_mouse_event({
 7800            let position_map = layout.position_map.clone();
 7801            let editor = self.editor.clone();
 7802
 7803            move |event: &MousePressureEvent, phase, window, cx| {
 7804                if phase == DispatchPhase::Bubble {
 7805                    editor.update(cx, |editor, cx| {
 7806                        Self::pressure_click(editor, &event, &position_map, window, cx);
 7807                    })
 7808                }
 7809            }
 7810        });
 7811
 7812        window.on_mouse_event({
 7813            let position_map = layout.position_map.clone();
 7814            let editor = self.editor.clone();
 7815            let split_side = self.split_side;
 7816
 7817            move |event: &MouseMoveEvent, phase, window, cx| {
 7818                if phase == DispatchPhase::Bubble {
 7819                    editor.update(cx, |editor, cx| {
 7820                        if editor.hover_state.focused(window, cx) {
 7821                            return;
 7822                        }
 7823                        if event.pressed_button == Some(MouseButton::Left)
 7824                            || event.pressed_button == Some(MouseButton::Middle)
 7825                        {
 7826                            Self::mouse_dragged(editor, event, &position_map, window, cx)
 7827                        }
 7828
 7829                        Self::mouse_moved(editor, event, &position_map, split_side, window, cx)
 7830                    });
 7831                }
 7832            }
 7833        });
 7834    }
 7835
 7836    fn shape_line_number(
 7837        &self,
 7838        text: SharedString,
 7839        color: Hsla,
 7840        window: &mut Window,
 7841    ) -> ShapedLine {
 7842        let run = TextRun {
 7843            len: text.len(),
 7844            font: self.style.text.font(),
 7845            color,
 7846            ..Default::default()
 7847        };
 7848        window.text_system().shape_line(
 7849            text,
 7850            self.style.text.font_size.to_pixels(window.rem_size()),
 7851            &[run],
 7852            None,
 7853        )
 7854    }
 7855
 7856    fn diff_hunk_hollow(status: DiffHunkStatus, cx: &mut App) -> bool {
 7857        let unstaged = status.has_secondary_hunk();
 7858        let unstaged_hollow = matches!(
 7859            ProjectSettings::get_global(cx).git.hunk_style,
 7860            GitHunkStyleSetting::UnstagedHollow
 7861        );
 7862
 7863        unstaged == unstaged_hollow
 7864    }
 7865
 7866    #[cfg(debug_assertions)]
 7867    fn layout_debug_ranges(
 7868        selections: &mut Vec<(PlayerColor, Vec<SelectionLayout>)>,
 7869        anchor_range: Range<Anchor>,
 7870        display_snapshot: &DisplaySnapshot,
 7871        cx: &App,
 7872    ) {
 7873        let theme = cx.theme();
 7874        text::debug::GlobalDebugRanges::with_locked(|debug_ranges| {
 7875            if debug_ranges.ranges.is_empty() {
 7876                return;
 7877            }
 7878            let buffer_snapshot = &display_snapshot.buffer_snapshot();
 7879            for (excerpt_buffer_snapshot, buffer_range, _) in
 7880                buffer_snapshot.range_to_buffer_ranges(anchor_range.start..anchor_range.end)
 7881            {
 7882                let buffer_range = excerpt_buffer_snapshot.anchor_after(buffer_range.start)
 7883                    ..excerpt_buffer_snapshot.anchor_before(buffer_range.end);
 7884                selections.extend(debug_ranges.ranges.iter().flat_map(|debug_range| {
 7885                    debug_range.ranges.iter().filter_map(|range| {
 7886                        let player_color = theme
 7887                            .players()
 7888                            .color_for_participant(debug_range.occurrence_index as u32 + 1);
 7889                        if range.start.buffer_id != excerpt_buffer_snapshot.remote_id() {
 7890                            return None;
 7891                        }
 7892                        let clipped_start = range
 7893                            .start
 7894                            .max(&buffer_range.start, &excerpt_buffer_snapshot);
 7895                        let clipped_end =
 7896                            range.end.min(&buffer_range.end, &excerpt_buffer_snapshot);
 7897                        let range = buffer_snapshot
 7898                            .buffer_anchor_range_to_anchor_range(*clipped_start..*clipped_end)?;
 7899                        let start = range.start.to_display_point(display_snapshot);
 7900                        let end = range.end.to_display_point(display_snapshot);
 7901                        let selection_layout = SelectionLayout {
 7902                            head: start,
 7903                            range: start..end,
 7904                            cursor_shape: CursorShape::Bar,
 7905                            is_newest: false,
 7906                            is_local: false,
 7907                            active_rows: start.row()..end.row(),
 7908                            user_name: Some(SharedString::new(debug_range.value.clone())),
 7909                        };
 7910                        Some((player_color, vec![selection_layout]))
 7911                    })
 7912                }));
 7913            }
 7914        });
 7915    }
 7916}
 7917
 7918struct Gutter<'a> {
 7919    line_height: Pixels,
 7920    range: Range<DisplayRow>,
 7921    scroll_position: gpui::Point<ScrollOffset>,
 7922    dimensions: &'a GutterDimensions,
 7923    hitbox: &'a Hitbox,
 7924    snapshot: &'a EditorSnapshot,
 7925    row_infos: &'a [RowInfo],
 7926}
 7927
 7928impl Gutter<'_> {
 7929    fn layout_item_skipping_folds(
 7930        &self,
 7931        display_row: DisplayRow,
 7932        render_item: impl Fn(&mut Context<'_, Editor>, &mut Window) -> AnyElement,
 7933        window: &mut Window,
 7934        cx: &mut Context<'_, Editor>,
 7935    ) -> Option<AnyElement> {
 7936        let row = MultiBufferRow(
 7937            DisplayPoint::new(display_row, 0)
 7938                .to_point(self.snapshot)
 7939                .row,
 7940        );
 7941        if self.snapshot.is_line_folded(row) {
 7942            return None;
 7943        }
 7944
 7945        self.layout_item(display_row, render_item, window, cx)
 7946    }
 7947
 7948    fn layout_item(
 7949        &self,
 7950        display_row: DisplayRow,
 7951        render_item: impl Fn(&mut Context<'_, Editor>, &mut Window) -> AnyElement,
 7952        window: &mut Window,
 7953        cx: &mut Context<'_, Editor>,
 7954    ) -> Option<AnyElement> {
 7955        if !self.range.contains(&display_row) {
 7956            return None;
 7957        }
 7958
 7959        if self
 7960            .row_infos
 7961            .get((display_row.0.saturating_sub(self.range.start.0)) as usize)
 7962            .is_some_and(|row_info| {
 7963                row_info.expand_info.is_some()
 7964                    || row_info
 7965                        .diff_status
 7966                        .is_some_and(|status| status.is_deleted())
 7967            })
 7968        {
 7969            return None;
 7970        }
 7971
 7972        let button = self.prepaint_button(render_item(cx, window), display_row, window, cx);
 7973        Some(button)
 7974    }
 7975
 7976    fn prepaint_button(
 7977        &self,
 7978        mut button: AnyElement,
 7979        row: DisplayRow,
 7980        window: &mut Window,
 7981        cx: &mut App,
 7982    ) -> AnyElement {
 7983        let available_space = size(
 7984            AvailableSpace::MinContent,
 7985            AvailableSpace::Definite(self.line_height),
 7986        );
 7987        let indicator_size = button.layout_as_root(available_space, window, cx);
 7988        let git_gutter_width = EditorElement::gutter_strip_width(self.line_height)
 7989            + self.dimensions.git_blame_entries_width.unwrap_or_default();
 7990
 7991        let x = git_gutter_width + px(2.);
 7992
 7993        let mut y = Pixels::from(
 7994            (row.as_f64() - self.scroll_position.y) * ScrollPixelOffset::from(self.line_height),
 7995        );
 7996        y += (self.line_height - indicator_size.height) / 2.;
 7997
 7998        button.prepaint_as_root(
 7999            self.hitbox.origin + point(x, y),
 8000            available_space,
 8001            window,
 8002            cx,
 8003        );
 8004        button
 8005    }
 8006}
 8007
 8008pub fn render_breadcrumb_text(
 8009    mut segments: Vec<HighlightedText>,
 8010    breadcrumb_font: Option<Font>,
 8011    prefix: Option<gpui::AnyElement>,
 8012    active_item: &dyn ItemHandle,
 8013    multibuffer_header: bool,
 8014    window: &mut Window,
 8015    cx: &App,
 8016) -> gpui::AnyElement {
 8017    const MAX_SEGMENTS: usize = 12;
 8018
 8019    let element = h_flex().flex_grow().text_ui(cx);
 8020
 8021    let prefix_end_ix = cmp::min(segments.len(), MAX_SEGMENTS / 2);
 8022    let suffix_start_ix = cmp::max(
 8023        prefix_end_ix,
 8024        segments.len().saturating_sub(MAX_SEGMENTS / 2),
 8025    );
 8026
 8027    if suffix_start_ix > prefix_end_ix {
 8028        segments.splice(
 8029            prefix_end_ix..suffix_start_ix,
 8030            Some(HighlightedText {
 8031                text: "β‹―".into(),
 8032                highlights: vec![],
 8033            }),
 8034        );
 8035    }
 8036
 8037    let highlighted_segments = segments.into_iter().enumerate().map(|(index, segment)| {
 8038        let mut text_style = window.text_style();
 8039        if let Some(font) = &breadcrumb_font {
 8040            text_style.font_family = font.family.clone();
 8041            text_style.font_features = font.features.clone();
 8042            text_style.font_style = font.style;
 8043            text_style.font_weight = font.weight;
 8044        }
 8045        text_style.color = Color::Muted.color(cx);
 8046
 8047        if index == 0
 8048            && !workspace::TabBarSettings::get_global(cx).show
 8049            && active_item.is_dirty(cx)
 8050            && let Some(styled_element) = apply_dirty_filename_style(&segment, &text_style, cx)
 8051        {
 8052            return styled_element;
 8053        }
 8054
 8055        StyledText::new(segment.text.replace('\n', " "))
 8056            .with_default_highlights(&text_style, segment.highlights)
 8057            .into_any()
 8058    });
 8059
 8060    let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
 8061        Label::new("β€Ί").color(Color::Placeholder).into_any_element()
 8062    });
 8063
 8064    let breadcrumbs_stack = h_flex()
 8065        .gap_1()
 8066        .when(multibuffer_header, |this| {
 8067            this.pl_2()
 8068                .border_l_1()
 8069                .border_color(cx.theme().colors().border.opacity(0.6))
 8070        })
 8071        .children(breadcrumbs);
 8072
 8073    let breadcrumbs = if let Some(prefix) = prefix {
 8074        h_flex().gap_1p5().child(prefix).child(breadcrumbs_stack)
 8075    } else {
 8076        breadcrumbs_stack
 8077    };
 8078
 8079    let editor = active_item
 8080        .downcast::<Editor>()
 8081        .map(|editor| editor.downgrade());
 8082
 8083    let has_project_path = active_item.project_path(cx).is_some();
 8084
 8085    match editor {
 8086        Some(editor) => element
 8087            .id("breadcrumb_container")
 8088            .when(!multibuffer_header, |this| this.overflow_x_scroll())
 8089            .child(
 8090                ButtonLike::new("toggle outline view")
 8091                    .child(breadcrumbs)
 8092                    .when(multibuffer_header, |this| {
 8093                        this.style(ButtonStyle::Transparent)
 8094                    })
 8095                    .when(!multibuffer_header, |this| {
 8096                        let focus_handle = editor.upgrade().unwrap().focus_handle(&cx);
 8097
 8098                        this.tooltip(Tooltip::element(move |_window, cx| {
 8099                            v_flex()
 8100                                .gap_1()
 8101                                .child(
 8102                                    h_flex()
 8103                                        .gap_1()
 8104                                        .justify_between()
 8105                                        .child(Label::new("Show Symbol Outline"))
 8106                                        .child(ui::KeyBinding::for_action_in(
 8107                                            &zed_actions::outline::ToggleOutline,
 8108                                            &focus_handle,
 8109                                            cx,
 8110                                        )),
 8111                                )
 8112                                .when(has_project_path, |this| {
 8113                                    this.child(
 8114                                        h_flex()
 8115                                            .gap_1()
 8116                                            .justify_between()
 8117                                            .pt_1()
 8118                                            .border_t_1()
 8119                                            .border_color(cx.theme().colors().border_variant)
 8120                                            .child(Label::new("Right-Click to Copy Path")),
 8121                                    )
 8122                                })
 8123                                .into_any_element()
 8124                        }))
 8125                        .on_click({
 8126                            let editor = editor.clone();
 8127                            move |_, window, cx| {
 8128                                if let Some((editor, callback)) = editor
 8129                                    .upgrade()
 8130                                    .zip(zed_actions::outline::TOGGLE_OUTLINE.get())
 8131                                {
 8132                                    callback(editor.to_any_view(), window, cx);
 8133                                }
 8134                            }
 8135                        })
 8136                        .when(has_project_path, |this| {
 8137                            this.on_right_click({
 8138                                let editor = editor.clone();
 8139                                move |_, _, cx| {
 8140                                    if let Some(abs_path) = editor.upgrade().and_then(|editor| {
 8141                                        editor.update(cx, |editor, cx| {
 8142                                            editor.target_file_abs_path(cx)
 8143                                        })
 8144                                    }) {
 8145                                        if let Some(path_str) = abs_path.to_str() {
 8146                                            cx.write_to_clipboard(ClipboardItem::new_string(
 8147                                                path_str.to_string(),
 8148                                            ));
 8149                                        }
 8150                                    }
 8151                                }
 8152                            })
 8153                        })
 8154                    }),
 8155            )
 8156            .into_any_element(),
 8157        None => element
 8158            .h(rems_from_px(22.)) // Match the height and padding of the `ButtonLike` in the other arm.
 8159            .pl_1()
 8160            .child(breadcrumbs)
 8161            .into_any_element(),
 8162    }
 8163}
 8164
 8165fn apply_dirty_filename_style(
 8166    segment: &HighlightedText,
 8167    text_style: &gpui::TextStyle,
 8168    cx: &App,
 8169) -> Option<gpui::AnyElement> {
 8170    let text = segment.text.replace('\n', " ");
 8171
 8172    let filename_position = std::path::Path::new(segment.text.as_ref())
 8173        .file_name()
 8174        .and_then(|f| {
 8175            let filename_str = f.to_string_lossy();
 8176            segment.text.rfind(filename_str.as_ref())
 8177        })?;
 8178
 8179    let bold_weight = FontWeight::BOLD;
 8180    let default_color = Color::Default.color(cx);
 8181
 8182    if filename_position == 0 {
 8183        let mut filename_style = text_style.clone();
 8184        filename_style.font_weight = bold_weight;
 8185        filename_style.color = default_color;
 8186
 8187        return Some(
 8188            StyledText::new(text)
 8189                .with_default_highlights(&filename_style, [])
 8190                .into_any(),
 8191        );
 8192    }
 8193
 8194    let highlight_style = gpui::HighlightStyle {
 8195        font_weight: Some(bold_weight),
 8196        color: Some(default_color),
 8197        ..Default::default()
 8198    };
 8199
 8200    let highlight = vec![(filename_position..text.len(), highlight_style)];
 8201    Some(
 8202        StyledText::new(text)
 8203            .with_default_highlights(text_style, highlight)
 8204            .into_any(),
 8205    )
 8206}
 8207
 8208fn file_status_label_color(file_status: Option<FileStatus>) -> Color {
 8209    file_status.map_or(Color::Default, |status| {
 8210        if status.is_conflicted() {
 8211            Color::Conflict
 8212        } else if status.is_modified() {
 8213            Color::Modified
 8214        } else if status.is_deleted() {
 8215            Color::Disabled
 8216        } else if status.is_created() {
 8217            Color::Created
 8218        } else {
 8219            Color::Default
 8220        }
 8221    })
 8222}
 8223
 8224pub(crate) fn header_jump_data(
 8225    editor_snapshot: &EditorSnapshot,
 8226    block_row_start: DisplayRow,
 8227    height: u32,
 8228    first_excerpt: &ExcerptBoundaryInfo,
 8229    latest_selection_anchors: &HashMap<BufferId, Anchor>,
 8230) -> JumpData {
 8231    let multibuffer_snapshot = editor_snapshot.buffer_snapshot();
 8232    let buffer = first_excerpt.buffer(multibuffer_snapshot);
 8233    let (jump_anchor, jump_buffer) = if let Some(anchor) =
 8234        latest_selection_anchors.get(&first_excerpt.buffer_id())
 8235        && let Some((jump_anchor, selection_buffer)) =
 8236            multibuffer_snapshot.anchor_to_buffer_anchor(*anchor)
 8237    {
 8238        (jump_anchor, selection_buffer)
 8239    } else {
 8240        (first_excerpt.range.primary.start, buffer)
 8241    };
 8242    let excerpt_start = first_excerpt.range.context.start;
 8243    let jump_position = language::ToPoint::to_point(&jump_anchor, jump_buffer);
 8244    let rows_from_excerpt_start = if jump_anchor == excerpt_start {
 8245        0
 8246    } else {
 8247        let excerpt_start_point = language::ToPoint::to_point(&excerpt_start, buffer);
 8248        jump_position.row.saturating_sub(excerpt_start_point.row)
 8249    };
 8250
 8251    let line_offset_from_top = (block_row_start.0 + height + rows_from_excerpt_start)
 8252        .saturating_sub(
 8253            editor_snapshot
 8254                .scroll_anchor
 8255                .scroll_position(&editor_snapshot.display_snapshot)
 8256                .y as u32,
 8257        );
 8258
 8259    JumpData::MultiBufferPoint {
 8260        anchor: jump_anchor,
 8261        position: jump_position,
 8262        line_offset_from_top,
 8263    }
 8264}
 8265
 8266pub(crate) fn render_buffer_header(
 8267    editor: &Entity<Editor>,
 8268    for_excerpt: &ExcerptBoundaryInfo,
 8269    is_folded: bool,
 8270    is_selected: bool,
 8271    is_sticky: bool,
 8272    jump_data: JumpData,
 8273    window: &mut Window,
 8274    cx: &mut App,
 8275) -> impl IntoElement {
 8276    let editor_read = editor.read(cx);
 8277    let multi_buffer = editor_read.buffer.read(cx);
 8278    let is_read_only = editor_read.read_only(cx);
 8279    let editor_handle: &dyn ItemHandle = editor;
 8280    let multibuffer_snapshot = multi_buffer.snapshot(cx);
 8281    let buffer = for_excerpt.buffer(&multibuffer_snapshot);
 8282
 8283    let breadcrumbs = if is_selected {
 8284        editor_read.breadcrumbs_inner(cx)
 8285    } else {
 8286        None
 8287    };
 8288
 8289    let buffer_id = for_excerpt.buffer_id();
 8290    let file_status = multi_buffer
 8291        .all_diff_hunks_expanded()
 8292        .then(|| editor_read.status_for_buffer_id(buffer_id, cx))
 8293        .flatten();
 8294    let indicator = multi_buffer.buffer(buffer_id).and_then(|buffer| {
 8295        let buffer = buffer.read(cx);
 8296        let indicator_color = match (buffer.has_conflict(), buffer.is_dirty()) {
 8297            (true, _) => Some(Color::Warning),
 8298            (_, true) => Some(Color::Accent),
 8299            (false, false) => None,
 8300        };
 8301        indicator_color.map(|indicator_color| Indicator::dot().color(indicator_color))
 8302    });
 8303
 8304    let include_root = editor_read
 8305        .project
 8306        .as_ref()
 8307        .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
 8308        .unwrap_or_default();
 8309    let file = buffer.file();
 8310    let can_open_excerpts = file.is_none_or(|file| file.can_open());
 8311    let path_style = file.map(|file| file.path_style(cx));
 8312    let relative_path = buffer.resolve_file_path(include_root, cx);
 8313    let (parent_path, filename) = if let Some(path) = &relative_path {
 8314        if let Some(path_style) = path_style {
 8315            let (dir, file_name) = path_style.split(path);
 8316            (dir.map(|dir| dir.to_owned()), Some(file_name.to_owned()))
 8317        } else {
 8318            (None, Some(path.clone()))
 8319        }
 8320    } else {
 8321        (None, None)
 8322    };
 8323    let focus_handle = editor_read.focus_handle(cx);
 8324    let colors = cx.theme().colors();
 8325
 8326    let header = div()
 8327        .id(("buffer-header", buffer_id.to_proto()))
 8328        .p(BUFFER_HEADER_PADDING)
 8329        .w_full()
 8330        .h(FILE_HEADER_HEIGHT as f32 * window.line_height())
 8331        .child(
 8332            h_flex()
 8333                .group("buffer-header-group")
 8334                .size_full()
 8335                .flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
 8336                .pl_1()
 8337                .pr_2()
 8338                .rounded_sm()
 8339                .gap_1p5()
 8340                .when(is_sticky, |el| el.shadow_md())
 8341                .border_1()
 8342                .map(|border| {
 8343                    let border_color =
 8344                        if is_selected && is_folded && focus_handle.contains_focused(window, cx) {
 8345                            colors.border_focused
 8346                        } else {
 8347                            colors.border
 8348                        };
 8349                    border.border_color(border_color)
 8350                })
 8351                .bg(colors.editor_subheader_background)
 8352                .hover(|style| style.bg(colors.element_hover))
 8353                .map(|header| {
 8354                    let editor = editor.clone();
 8355                    let buffer_id = for_excerpt.buffer_id();
 8356                    let toggle_chevron_icon =
 8357                        FileIcons::get_chevron_icon(!is_folded, cx).map(Icon::from_path);
 8358                    let button_size = rems_from_px(28.);
 8359
 8360                    header.child(
 8361                        div()
 8362                            .hover(|style| style.bg(colors.element_selected))
 8363                            .rounded_xs()
 8364                            .child(
 8365                                ButtonLike::new("toggle-buffer-fold")
 8366                                    .style(ButtonStyle::Transparent)
 8367                                    .height(button_size.into())
 8368                                    .width(button_size)
 8369                                    .children(toggle_chevron_icon)
 8370                                    .tooltip({
 8371                                        let focus_handle = focus_handle.clone();
 8372                                        let is_folded_for_tooltip = is_folded;
 8373                                        move |_window, cx| {
 8374                                            Tooltip::with_meta_in(
 8375                                                if is_folded_for_tooltip {
 8376                                                    "Unfold Excerpt"
 8377                                                } else {
 8378                                                    "Fold Excerpt"
 8379                                                },
 8380                                                Some(&ToggleFold),
 8381                                                format!(
 8382                                                    "{} to toggle all",
 8383                                                    text_for_keystroke(
 8384                                                        &Modifiers::alt(),
 8385                                                        "click",
 8386                                                        cx
 8387                                                    )
 8388                                                ),
 8389                                                &focus_handle,
 8390                                                cx,
 8391                                            )
 8392                                        }
 8393                                    })
 8394                                    .on_click(move |event, window, cx| {
 8395                                        if event.modifiers().alt {
 8396                                            editor.update(cx, |editor, cx| {
 8397                                                editor.toggle_fold_all(&ToggleFoldAll, window, cx);
 8398                                            });
 8399                                        } else {
 8400                                            if is_folded {
 8401                                                editor.update(cx, |editor, cx| {
 8402                                                    editor.unfold_buffer(buffer_id, cx);
 8403                                                });
 8404                                            } else {
 8405                                                editor.update(cx, |editor, cx| {
 8406                                                    editor.fold_buffer(buffer_id, cx);
 8407                                                });
 8408                                            }
 8409                                        }
 8410                                    }),
 8411                            ),
 8412                    )
 8413                })
 8414                .children(
 8415                    editor_read
 8416                        .addons
 8417                        .values()
 8418                        .filter_map(|addon| {
 8419                            addon.render_buffer_header_controls(for_excerpt, buffer, window, cx)
 8420                        })
 8421                        .take(1),
 8422                )
 8423                .when(!is_read_only, |this| {
 8424                    this.child(
 8425                        h_flex()
 8426                            .size_3()
 8427                            .justify_center()
 8428                            .flex_shrink_0()
 8429                            .children(indicator),
 8430                    )
 8431                })
 8432                .child(
 8433                    h_flex()
 8434                        .cursor_pointer()
 8435                        .id("path_header_block")
 8436                        .min_w_0()
 8437                        .size_full()
 8438                        .gap_1()
 8439                        .justify_between()
 8440                        .overflow_hidden()
 8441                        .child(h_flex().min_w_0().flex_1().gap_0p5().overflow_hidden().map(
 8442                            |path_header| {
 8443                                let filename = filename
 8444                                    .map(SharedString::from)
 8445                                    .unwrap_or_else(|| "untitled".into());
 8446
 8447                                let full_path = match parent_path.as_deref() {
 8448                                    Some(parent) if !parent.is_empty() => {
 8449                                        format!("{}{}", parent, filename.as_str())
 8450                                    }
 8451                                    _ => filename.as_str().to_string(),
 8452                                };
 8453
 8454                                path_header
 8455                                    .child(
 8456                                        ButtonLike::new("filename-button")
 8457                                            .when(ItemSettings::get_global(cx).file_icons, |this| {
 8458                                                let path = path::Path::new(filename.as_str());
 8459                                                let icon = FileIcons::get_icon(path, cx)
 8460                                                    .unwrap_or_default();
 8461
 8462                                                this.child(
 8463                                                    Icon::from_path(icon).color(Color::Muted),
 8464                                                )
 8465                                            })
 8466                                            .child(
 8467                                                Label::new(filename)
 8468                                                    .single_line()
 8469                                                    .color(file_status_label_color(file_status))
 8470                                                    .buffer_font(cx)
 8471                                                    .when(
 8472                                                        file_status.is_some_and(|s| s.is_deleted()),
 8473                                                        |label| label.strikethrough(),
 8474                                                    ),
 8475                                            )
 8476                                            .tooltip(move |_, cx| {
 8477                                                Tooltip::with_meta(
 8478                                                    "Open File",
 8479                                                    None,
 8480                                                    full_path.clone(),
 8481                                                    cx,
 8482                                                )
 8483                                            })
 8484                                            .on_click(window.listener_for(editor, {
 8485                                                let jump_data = jump_data.clone();
 8486                                                move |editor, e: &ClickEvent, window, cx| {
 8487                                                    editor.open_excerpts_common(
 8488                                                        Some(jump_data.clone()),
 8489                                                        e.modifiers().secondary(),
 8490                                                        window,
 8491                                                        cx,
 8492                                                    );
 8493                                                }
 8494                                            })),
 8495                                    )
 8496                                    .when_some(parent_path, |then, path| {
 8497                                        then.child(
 8498                                            Label::new(path)
 8499                                                .buffer_font(cx)
 8500                                                .truncate_start()
 8501                                                .color(
 8502                                                    if file_status
 8503                                                        .is_some_and(FileStatus::is_deleted)
 8504                                                    {
 8505                                                        Color::Custom(colors.text_disabled)
 8506                                                    } else {
 8507                                                        Color::Custom(colors.text_muted)
 8508                                                    },
 8509                                                ),
 8510                                        )
 8511                                    })
 8512                                    .when(!buffer.capability.editable(), |el| {
 8513                                        el.child(Icon::new(IconName::FileLock).color(Color::Muted))
 8514                                    })
 8515                                    .when_some(breadcrumbs, |then, breadcrumbs| {
 8516                                        let font = theme_settings::ThemeSettings::get_global(cx)
 8517                                            .buffer_font
 8518                                            .clone();
 8519                                        then.child(render_breadcrumb_text(
 8520                                            breadcrumbs,
 8521                                            Some(font),
 8522                                            None,
 8523                                            editor_handle,
 8524                                            true,
 8525                                            window,
 8526                                            cx,
 8527                                        ))
 8528                                    })
 8529                            },
 8530                        ))
 8531                        .when(can_open_excerpts && relative_path.is_some(), |this| {
 8532                            this.child(
 8533                                div()
 8534                                    .when(!is_selected, |this| {
 8535                                        this.visible_on_hover("buffer-header-group")
 8536                                    })
 8537                                    .child(
 8538                                        Button::new("open-file-button", "Open File")
 8539                                            .style(ButtonStyle::OutlinedGhost)
 8540                                            .when(is_selected, |this| {
 8541                                                this.key_binding(KeyBinding::for_action_in(
 8542                                                    &OpenExcerpts,
 8543                                                    &focus_handle,
 8544                                                    cx,
 8545                                                ))
 8546                                            })
 8547                                            .on_click(window.listener_for(editor, {
 8548                                                let jump_data = jump_data.clone();
 8549                                                move |editor, e: &ClickEvent, window, cx| {
 8550                                                    editor.open_excerpts_common(
 8551                                                        Some(jump_data.clone()),
 8552                                                        e.modifiers().secondary(),
 8553                                                        window,
 8554                                                        cx,
 8555                                                    );
 8556                                                }
 8557                                            })),
 8558                                    ),
 8559                            )
 8560                        })
 8561                        .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
 8562                        .on_click(window.listener_for(editor, {
 8563                            let buffer_id = for_excerpt.buffer_id();
 8564                            move |editor, e: &ClickEvent, window, cx| {
 8565                                if e.modifiers().alt {
 8566                                    editor.open_excerpts_common(
 8567                                        Some(jump_data.clone()),
 8568                                        e.modifiers().secondary(),
 8569                                        window,
 8570                                        cx,
 8571                                    );
 8572                                    return;
 8573                                }
 8574
 8575                                if is_folded {
 8576                                    editor.unfold_buffer(buffer_id, cx);
 8577                                } else {
 8578                                    editor.fold_buffer(buffer_id, cx);
 8579                                }
 8580                            }
 8581                        })),
 8582                ),
 8583        );
 8584
 8585    let file = buffer.file().cloned();
 8586    let editor = editor.clone();
 8587
 8588    right_click_menu("buffer-header-context-menu")
 8589        .trigger(move |_, _, _| header)
 8590        .menu(move |window, cx| {
 8591            let menu_context = focus_handle.clone();
 8592            let editor = editor.clone();
 8593            let file = file.clone();
 8594            ContextMenu::build(window, cx, move |mut menu, window, cx| {
 8595                if let Some(file) = file
 8596                    && let Some(project) = editor.read(cx).project()
 8597                    && let Some(worktree) =
 8598                        project.read(cx).worktree_for_id(file.worktree_id(cx), cx)
 8599                {
 8600                    let path_style = file.path_style(cx);
 8601                    let worktree = worktree.read(cx);
 8602                    let relative_path = file.path();
 8603                    let entry_for_path = worktree.entry_for_path(relative_path);
 8604                    let abs_path = entry_for_path.map(|e| {
 8605                        e.canonical_path
 8606                            .as_deref()
 8607                            .map_or_else(|| worktree.absolutize(relative_path), Path::to_path_buf)
 8608                    });
 8609                    let has_relative_path = worktree.root_entry().is_some_and(Entry::is_dir);
 8610
 8611                    let parent_abs_path = abs_path
 8612                        .as_ref()
 8613                        .and_then(|abs_path| Some(abs_path.parent()?.to_path_buf()));
 8614                    let relative_path = has_relative_path
 8615                        .then_some(relative_path)
 8616                        .map(ToOwned::to_owned);
 8617
 8618                    let visible_in_project_panel = relative_path.is_some() && worktree.is_visible();
 8619                    let reveal_in_project_panel = entry_for_path
 8620                        .filter(|_| visible_in_project_panel)
 8621                        .map(|entry| entry.id);
 8622                    menu = menu
 8623                        .when_some(abs_path, |menu, abs_path| {
 8624                            menu.entry(
 8625                                "Copy Path",
 8626                                Some(Box::new(zed_actions::workspace::CopyPath)),
 8627                                window.handler_for(&editor, move |_, _, cx| {
 8628                                    cx.write_to_clipboard(ClipboardItem::new_string(
 8629                                        abs_path.to_string_lossy().into_owned(),
 8630                                    ));
 8631                                }),
 8632                            )
 8633                        })
 8634                        .when_some(relative_path, |menu, relative_path| {
 8635                            menu.entry(
 8636                                "Copy Relative Path",
 8637                                Some(Box::new(zed_actions::workspace::CopyRelativePath)),
 8638                                window.handler_for(&editor, move |_, _, cx| {
 8639                                    cx.write_to_clipboard(ClipboardItem::new_string(
 8640                                        relative_path.display(path_style).to_string(),
 8641                                    ));
 8642                                }),
 8643                            )
 8644                        })
 8645                        .when(
 8646                            reveal_in_project_panel.is_some() || parent_abs_path.is_some(),
 8647                            |menu| menu.separator(),
 8648                        )
 8649                        .when_some(reveal_in_project_panel, |menu, entry_id| {
 8650                            menu.entry(
 8651                                "Reveal In Project Panel",
 8652                                Some(Box::new(RevealInProjectPanel::default())),
 8653                                window.handler_for(&editor, move |editor, _, cx| {
 8654                                    if let Some(project) = &mut editor.project {
 8655                                        project.update(cx, |_, cx| {
 8656                                            cx.emit(project::Event::RevealInProjectPanel(entry_id))
 8657                                        });
 8658                                    }
 8659                                }),
 8660                            )
 8661                        })
 8662                        .when_some(parent_abs_path, |menu, parent_abs_path| {
 8663                            menu.entry(
 8664                                "Open in Terminal",
 8665                                Some(Box::new(OpenInTerminal)),
 8666                                window.handler_for(&editor, move |_, window, cx| {
 8667                                    window.dispatch_action(
 8668                                        OpenTerminal {
 8669                                            working_directory: parent_abs_path.clone(),
 8670                                            local: false,
 8671                                        }
 8672                                        .boxed_clone(),
 8673                                        cx,
 8674                                    );
 8675                                }),
 8676                            )
 8677                        });
 8678                }
 8679
 8680                menu.context(menu_context)
 8681            })
 8682        })
 8683}
 8684
 8685fn render_inline_blame_entry(
 8686    blame_entry: BlameEntry,
 8687    style: &EditorStyle,
 8688    cx: &mut App,
 8689) -> Option<AnyElement> {
 8690    let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
 8691    renderer.render_inline_blame_entry(&style.text, blame_entry, cx)
 8692}
 8693
 8694fn render_blame_entry_popover(
 8695    blame_entry: BlameEntry,
 8696    scroll_handle: ScrollHandle,
 8697    commit_message: Option<ParsedCommitMessage>,
 8698    markdown: Entity<Markdown>,
 8699    workspace: WeakEntity<Workspace>,
 8700    blame: &Entity<GitBlame>,
 8701    buffer: BufferId,
 8702    window: &mut Window,
 8703    cx: &mut App,
 8704) -> Option<AnyElement> {
 8705    if markdown.read(cx).is_parsing() {
 8706        return None;
 8707    }
 8708
 8709    let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
 8710    let blame = blame.read(cx);
 8711    let repository = blame.repository(cx, buffer)?;
 8712    renderer.render_blame_entry_popover(
 8713        blame_entry,
 8714        scroll_handle,
 8715        commit_message,
 8716        markdown,
 8717        repository,
 8718        workspace,
 8719        window,
 8720        cx,
 8721    )
 8722}
 8723
 8724fn render_blame_entry(
 8725    ix: usize,
 8726    blame: &Entity<GitBlame>,
 8727    blame_entry: BlameEntry,
 8728    style: &EditorStyle,
 8729    last_used_color: &mut Option<(Hsla, Oid)>,
 8730    editor: Entity<Editor>,
 8731    workspace: Entity<Workspace>,
 8732    buffer: BufferId,
 8733    renderer: &dyn BlameRenderer,
 8734    window: &mut Window,
 8735    cx: &mut App,
 8736) -> Option<AnyElement> {
 8737    let index: u32 = blame_entry.sha.into();
 8738    let mut sha_color = cx.theme().players().color_for_participant(index).cursor;
 8739
 8740    // If the last color we used is the same as the one we get for this line, but
 8741    // the commit SHAs are different, then we try again to get a different color.
 8742    if let Some((color, sha)) = *last_used_color
 8743        && sha != blame_entry.sha
 8744        && color == sha_color
 8745    {
 8746        sha_color = cx.theme().players().color_for_participant(index + 1).cursor;
 8747    }
 8748    last_used_color.replace((sha_color, blame_entry.sha));
 8749
 8750    let blame = blame.read(cx);
 8751    let details = blame.details_for_entry(buffer, &blame_entry);
 8752    let repository = blame.repository(cx, buffer)?;
 8753    renderer.render_blame_entry(
 8754        &style.text,
 8755        blame_entry,
 8756        details,
 8757        repository,
 8758        workspace.downgrade(),
 8759        editor,
 8760        ix,
 8761        sha_color,
 8762        window,
 8763        cx,
 8764    )
 8765}
 8766
 8767#[derive(Debug)]
 8768pub(crate) struct LineWithInvisibles {
 8769    fragments: SmallVec<[LineFragment; 1]>,
 8770    invisibles: Vec<Invisible>,
 8771    len: usize,
 8772    pub(crate) width: Pixels,
 8773    font_size: Pixels,
 8774}
 8775
 8776enum LineFragment {
 8777    Text(ShapedLine),
 8778    Element {
 8779        id: ChunkRendererId,
 8780        element: Option<AnyElement>,
 8781        size: Size<Pixels>,
 8782        len: usize,
 8783    },
 8784}
 8785
 8786impl fmt::Debug for LineFragment {
 8787    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 8788        match self {
 8789            LineFragment::Text(shaped_line) => f.debug_tuple("Text").field(shaped_line).finish(),
 8790            LineFragment::Element { size, len, .. } => f
 8791                .debug_struct("Element")
 8792                .field("size", size)
 8793                .field("len", len)
 8794                .finish(),
 8795        }
 8796    }
 8797}
 8798
 8799impl LineWithInvisibles {
 8800    fn from_chunks<'a>(
 8801        chunks: impl Iterator<Item = HighlightedChunk<'a>>,
 8802        editor_style: &EditorStyle,
 8803        max_line_len: usize,
 8804        max_line_count: usize,
 8805        editor_mode: &EditorMode,
 8806        text_width: Pixels,
 8807        is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
 8808        bg_segments_per_row: &[Vec<(Range<DisplayPoint>, Hsla)>],
 8809        window: &mut Window,
 8810        cx: &mut App,
 8811    ) -> Vec<Self> {
 8812        let text_style = &editor_style.text;
 8813        let mut layouts = Vec::with_capacity(max_line_count);
 8814        let mut fragments: SmallVec<[LineFragment; 1]> = SmallVec::new();
 8815        let mut line = String::new();
 8816        let mut invisibles = Vec::new();
 8817        let mut width = Pixels::ZERO;
 8818        let mut len = 0;
 8819        let mut styles = Vec::new();
 8820        let mut non_whitespace_added = false;
 8821        let mut row = 0;
 8822        let mut line_exceeded_max_len = false;
 8823        let font_size = text_style.font_size.to_pixels(window.rem_size());
 8824        let min_contrast = EditorSettings::get_global(cx).minimum_contrast_for_highlights;
 8825
 8826        let ellipsis = SharedString::from("β‹―");
 8827
 8828        for highlighted_chunk in chunks.chain([HighlightedChunk {
 8829            text: "\n",
 8830            style: None,
 8831            is_tab: false,
 8832            is_inlay: false,
 8833            replacement: None,
 8834        }]) {
 8835            if let Some(replacement) = highlighted_chunk.replacement {
 8836                if !line.is_empty() {
 8837                    let segments = bg_segments_per_row.get(row).map(|v| &v[..]).unwrap_or(&[]);
 8838                    let text_runs: &[TextRun] = if segments.is_empty() {
 8839                        &styles
 8840                    } else {
 8841                        &Self::split_runs_by_bg_segments(&styles, segments, min_contrast, len)
 8842                    };
 8843                    let shaped_line = window.text_system().shape_line(
 8844                        line.clone().into(),
 8845                        font_size,
 8846                        text_runs,
 8847                        None,
 8848                    );
 8849                    width += shaped_line.width;
 8850                    len += shaped_line.len;
 8851                    fragments.push(LineFragment::Text(shaped_line));
 8852                    line.clear();
 8853                    styles.clear();
 8854                }
 8855
 8856                match replacement {
 8857                    ChunkReplacement::Renderer(renderer) => {
 8858                        let available_width = if renderer.constrain_width {
 8859                            let chunk = if highlighted_chunk.text == ellipsis.as_ref() {
 8860                                ellipsis.clone()
 8861                            } else {
 8862                                SharedString::from(Arc::from(highlighted_chunk.text))
 8863                            };
 8864                            let shaped_line = window.text_system().shape_line(
 8865                                chunk,
 8866                                font_size,
 8867                                &[text_style.to_run(highlighted_chunk.text.len())],
 8868                                None,
 8869                            );
 8870                            AvailableSpace::Definite(shaped_line.width)
 8871                        } else {
 8872                            AvailableSpace::MinContent
 8873                        };
 8874
 8875                        let mut element = (renderer.render)(&mut ChunkRendererContext {
 8876                            context: cx,
 8877                            window,
 8878                            max_width: text_width,
 8879                        });
 8880                        let line_height = text_style.line_height_in_pixels(window.rem_size());
 8881                        let size = element.layout_as_root(
 8882                            size(available_width, AvailableSpace::Definite(line_height)),
 8883                            window,
 8884                            cx,
 8885                        );
 8886
 8887                        width += size.width;
 8888                        len += highlighted_chunk.text.len();
 8889                        fragments.push(LineFragment::Element {
 8890                            id: renderer.id,
 8891                            element: Some(element),
 8892                            size,
 8893                            len: highlighted_chunk.text.len(),
 8894                        });
 8895                    }
 8896                    ChunkReplacement::Str(x) => {
 8897                        let text_style = if let Some(style) = highlighted_chunk.style {
 8898                            Cow::Owned(text_style.clone().highlight(style))
 8899                        } else {
 8900                            Cow::Borrowed(text_style)
 8901                        };
 8902
 8903                        let run = TextRun {
 8904                            len: x.len(),
 8905                            font: text_style.font(),
 8906                            color: text_style.color,
 8907                            background_color: text_style.background_color,
 8908                            underline: text_style.underline,
 8909                            strikethrough: text_style.strikethrough,
 8910                        };
 8911                        let line_layout = window
 8912                            .text_system()
 8913                            .shape_line(x, font_size, &[run], None)
 8914                            .with_len(highlighted_chunk.text.len());
 8915
 8916                        width += line_layout.width;
 8917                        len += highlighted_chunk.text.len();
 8918                        fragments.push(LineFragment::Text(line_layout))
 8919                    }
 8920                }
 8921            } else {
 8922                for (ix, mut line_chunk) in highlighted_chunk.text.split('\n').enumerate() {
 8923                    if ix > 0 {
 8924                        let segments = bg_segments_per_row.get(row).map(|v| &v[..]).unwrap_or(&[]);
 8925                        let text_runs = if segments.is_empty() {
 8926                            &styles
 8927                        } else {
 8928                            &Self::split_runs_by_bg_segments(&styles, segments, min_contrast, len)
 8929                        };
 8930                        let shaped_line = window.text_system().shape_line(
 8931                            line.clone().into(),
 8932                            font_size,
 8933                            text_runs,
 8934                            None,
 8935                        );
 8936                        width += shaped_line.width;
 8937                        len += shaped_line.len;
 8938                        fragments.push(LineFragment::Text(shaped_line));
 8939                        layouts.push(Self {
 8940                            width: mem::take(&mut width),
 8941                            len: mem::take(&mut len),
 8942                            fragments: mem::take(&mut fragments),
 8943                            invisibles: std::mem::take(&mut invisibles),
 8944                            font_size,
 8945                        });
 8946
 8947                        line.clear();
 8948                        styles.clear();
 8949                        row += 1;
 8950                        line_exceeded_max_len = false;
 8951                        non_whitespace_added = false;
 8952                        if row == max_line_count {
 8953                            return layouts;
 8954                        }
 8955                    }
 8956
 8957                    if !line_chunk.is_empty() && !line_exceeded_max_len {
 8958                        let text_style = if let Some(style) = highlighted_chunk.style {
 8959                            Cow::Owned(text_style.clone().highlight(style))
 8960                        } else {
 8961                            Cow::Borrowed(text_style)
 8962                        };
 8963
 8964                        if line.len() + line_chunk.len() > max_line_len {
 8965                            let mut chunk_len = max_line_len - line.len();
 8966                            while !line_chunk.is_char_boundary(chunk_len) {
 8967                                chunk_len -= 1;
 8968                            }
 8969                            line_chunk = &line_chunk[..chunk_len];
 8970                            line_exceeded_max_len = true;
 8971                        }
 8972
 8973                        styles.push(TextRun {
 8974                            len: line_chunk.len(),
 8975                            font: text_style.font(),
 8976                            color: text_style.color,
 8977                            background_color: text_style.background_color,
 8978                            underline: text_style.underline,
 8979                            strikethrough: text_style.strikethrough,
 8980                        });
 8981
 8982                        if editor_mode.is_full() && !highlighted_chunk.is_inlay {
 8983                            // Line wrap pads its contents with fake whitespaces,
 8984                            // avoid printing them
 8985                            let is_soft_wrapped = is_row_soft_wrapped(row);
 8986                            if highlighted_chunk.is_tab {
 8987                                if non_whitespace_added || !is_soft_wrapped {
 8988                                    invisibles.push(Invisible::Tab {
 8989                                        line_start_offset: line.len(),
 8990                                        line_end_offset: line.len() + line_chunk.len(),
 8991                                    });
 8992                                }
 8993                            } else {
 8994                                invisibles.extend(line_chunk.char_indices().filter_map(
 8995                                    |(index, c)| {
 8996                                        let is_whitespace = c.is_whitespace();
 8997                                        non_whitespace_added |= !is_whitespace;
 8998                                        if is_whitespace
 8999                                            && (non_whitespace_added || !is_soft_wrapped)
 9000                                        {
 9001                                            Some(Invisible::Whitespace {
 9002                                                line_offset: line.len() + index,
 9003                                            })
 9004                                        } else {
 9005                                            None
 9006                                        }
 9007                                    },
 9008                                ))
 9009                            }
 9010                        }
 9011
 9012                        line.push_str(line_chunk);
 9013                    }
 9014                }
 9015            }
 9016        }
 9017
 9018        layouts
 9019    }
 9020
 9021    /// Takes text runs and non-overlapping left-to-right background ranges with color.
 9022    /// Returns new text runs with adjusted contrast as per background ranges.
 9023    fn split_runs_by_bg_segments(
 9024        text_runs: &[TextRun],
 9025        bg_segments: &[(Range<DisplayPoint>, Hsla)],
 9026        min_contrast: f32,
 9027        start_col_offset: usize,
 9028    ) -> Vec<TextRun> {
 9029        let mut output_runs: Vec<TextRun> = Vec::with_capacity(text_runs.len());
 9030        let mut line_col = start_col_offset;
 9031        let mut segment_ix = 0usize;
 9032
 9033        for text_run in text_runs.iter() {
 9034            let run_start_col = line_col;
 9035            let run_end_col = run_start_col + text_run.len;
 9036            while segment_ix < bg_segments.len()
 9037                && (bg_segments[segment_ix].0.end.column() as usize) <= run_start_col
 9038            {
 9039                segment_ix += 1;
 9040            }
 9041            let mut cursor_col = run_start_col;
 9042            let mut local_segment_ix = segment_ix;
 9043            while local_segment_ix < bg_segments.len() {
 9044                let (range, segment_color) = &bg_segments[local_segment_ix];
 9045                let segment_start_col = range.start.column() as usize;
 9046                let segment_end_col = range.end.column() as usize;
 9047                if segment_start_col >= run_end_col {
 9048                    break;
 9049                }
 9050                if segment_start_col > cursor_col {
 9051                    let span_len = segment_start_col - cursor_col;
 9052                    output_runs.push(TextRun {
 9053                        len: span_len,
 9054                        font: text_run.font.clone(),
 9055                        color: text_run.color,
 9056                        background_color: text_run.background_color,
 9057                        underline: text_run.underline,
 9058                        strikethrough: text_run.strikethrough,
 9059                    });
 9060                    cursor_col = segment_start_col;
 9061                }
 9062                let segment_slice_end_col = segment_end_col.min(run_end_col);
 9063                if segment_slice_end_col > cursor_col {
 9064                    let new_text_color =
 9065                        ensure_minimum_contrast(text_run.color, *segment_color, min_contrast);
 9066                    output_runs.push(TextRun {
 9067                        len: segment_slice_end_col - cursor_col,
 9068                        font: text_run.font.clone(),
 9069                        color: new_text_color,
 9070                        background_color: text_run.background_color,
 9071                        underline: text_run.underline,
 9072                        strikethrough: text_run.strikethrough,
 9073                    });
 9074                    cursor_col = segment_slice_end_col;
 9075                }
 9076                if segment_end_col >= run_end_col {
 9077                    break;
 9078                }
 9079                local_segment_ix += 1;
 9080            }
 9081            if cursor_col < run_end_col {
 9082                output_runs.push(TextRun {
 9083                    len: run_end_col - cursor_col,
 9084                    font: text_run.font.clone(),
 9085                    color: text_run.color,
 9086                    background_color: text_run.background_color,
 9087                    underline: text_run.underline,
 9088                    strikethrough: text_run.strikethrough,
 9089                });
 9090            }
 9091            line_col = run_end_col;
 9092            segment_ix = local_segment_ix;
 9093        }
 9094        output_runs
 9095    }
 9096
 9097    fn prepaint(
 9098        &mut self,
 9099        line_height: Pixels,
 9100        scroll_position: gpui::Point<ScrollOffset>,
 9101        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 9102        row: DisplayRow,
 9103        content_origin: gpui::Point<Pixels>,
 9104        line_elements: &mut SmallVec<[AnyElement; 1]>,
 9105        window: &mut Window,
 9106        cx: &mut App,
 9107    ) {
 9108        let line_y = f32::from(line_height) * Pixels::from(row.as_f64() - scroll_position.y);
 9109        self.prepaint_with_custom_offset(
 9110            line_height,
 9111            scroll_pixel_position,
 9112            content_origin,
 9113            line_y,
 9114            line_elements,
 9115            window,
 9116            cx,
 9117        );
 9118    }
 9119
 9120    fn prepaint_with_custom_offset(
 9121        &mut self,
 9122        line_height: Pixels,
 9123        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
 9124        content_origin: gpui::Point<Pixels>,
 9125        line_y: Pixels,
 9126        line_elements: &mut SmallVec<[AnyElement; 1]>,
 9127        window: &mut Window,
 9128        cx: &mut App,
 9129    ) {
 9130        let mut fragment_origin =
 9131            content_origin + gpui::point(Pixels::from(-scroll_pixel_position.x), line_y);
 9132        for fragment in &mut self.fragments {
 9133            match fragment {
 9134                LineFragment::Text(line) => {
 9135                    fragment_origin.x += line.width;
 9136                }
 9137                LineFragment::Element { element, size, .. } => {
 9138                    let mut element = element
 9139                        .take()
 9140                        .expect("you can't prepaint LineWithInvisibles twice");
 9141
 9142                    // Center the element vertically within the line.
 9143                    let mut element_origin = fragment_origin;
 9144                    element_origin.y += (line_height - size.height) / 2.;
 9145                    element.prepaint_at(element_origin, window, cx);
 9146                    line_elements.push(element);
 9147
 9148                    fragment_origin.x += size.width;
 9149                }
 9150            }
 9151        }
 9152    }
 9153
 9154    fn draw(
 9155        &self,
 9156        layout: &EditorLayout,
 9157        row: DisplayRow,
 9158        content_origin: gpui::Point<Pixels>,
 9159        whitespace_setting: ShowWhitespaceSetting,
 9160        selection_ranges: &[Range<DisplayPoint>],
 9161        window: &mut Window,
 9162        cx: &mut App,
 9163    ) {
 9164        self.draw_with_custom_offset(
 9165            layout,
 9166            row,
 9167            content_origin,
 9168            layout.position_map.line_height
 9169                * (row.as_f64() - layout.position_map.scroll_position.y) as f32,
 9170            whitespace_setting,
 9171            selection_ranges,
 9172            window,
 9173            cx,
 9174        );
 9175    }
 9176
 9177    fn draw_with_custom_offset(
 9178        &self,
 9179        layout: &EditorLayout,
 9180        row: DisplayRow,
 9181        content_origin: gpui::Point<Pixels>,
 9182        line_y: Pixels,
 9183        whitespace_setting: ShowWhitespaceSetting,
 9184        selection_ranges: &[Range<DisplayPoint>],
 9185        window: &mut Window,
 9186        cx: &mut App,
 9187    ) {
 9188        let line_height = layout.position_map.line_height;
 9189        let mut fragment_origin = content_origin
 9190            + gpui::point(
 9191                Pixels::from(-layout.position_map.scroll_pixel_position.x),
 9192                line_y,
 9193            );
 9194
 9195        for fragment in &self.fragments {
 9196            match fragment {
 9197                LineFragment::Text(line) => {
 9198                    line.paint(
 9199                        fragment_origin,
 9200                        line_height,
 9201                        layout.text_align,
 9202                        Some(layout.content_width),
 9203                        window,
 9204                        cx,
 9205                    )
 9206                    .log_err();
 9207                    fragment_origin.x += line.width;
 9208                }
 9209                LineFragment::Element { size, .. } => {
 9210                    fragment_origin.x += size.width;
 9211                }
 9212            }
 9213        }
 9214
 9215        self.draw_invisibles(
 9216            selection_ranges,
 9217            layout,
 9218            content_origin,
 9219            line_y,
 9220            row,
 9221            line_height,
 9222            whitespace_setting,
 9223            window,
 9224            cx,
 9225        );
 9226    }
 9227
 9228    fn draw_background(
 9229        &self,
 9230        layout: &EditorLayout,
 9231        row: DisplayRow,
 9232        content_origin: gpui::Point<Pixels>,
 9233        window: &mut Window,
 9234        cx: &mut App,
 9235    ) {
 9236        let line_height = layout.position_map.line_height;
 9237        let line_y = line_height * (row.as_f64() - layout.position_map.scroll_position.y) as f32;
 9238
 9239        let mut fragment_origin = content_origin
 9240            + gpui::point(
 9241                Pixels::from(-layout.position_map.scroll_pixel_position.x),
 9242                line_y,
 9243            );
 9244
 9245        for fragment in &self.fragments {
 9246            match fragment {
 9247                LineFragment::Text(line) => {
 9248                    line.paint_background(
 9249                        fragment_origin,
 9250                        line_height,
 9251                        layout.text_align,
 9252                        Some(layout.content_width),
 9253                        window,
 9254                        cx,
 9255                    )
 9256                    .log_err();
 9257                    fragment_origin.x += line.width;
 9258                }
 9259                LineFragment::Element { size, .. } => {
 9260                    fragment_origin.x += size.width;
 9261                }
 9262            }
 9263        }
 9264    }
 9265
 9266    fn draw_invisibles(
 9267        &self,
 9268        selection_ranges: &[Range<DisplayPoint>],
 9269        layout: &EditorLayout,
 9270        content_origin: gpui::Point<Pixels>,
 9271        line_y: Pixels,
 9272        row: DisplayRow,
 9273        line_height: Pixels,
 9274        whitespace_setting: ShowWhitespaceSetting,
 9275        window: &mut Window,
 9276        cx: &mut App,
 9277    ) {
 9278        let extract_whitespace_info = |invisible: &Invisible| {
 9279            let (token_offset, token_end_offset, invisible_symbol) = match invisible {
 9280                Invisible::Tab {
 9281                    line_start_offset,
 9282                    line_end_offset,
 9283                } => (*line_start_offset, *line_end_offset, &layout.tab_invisible),
 9284                Invisible::Whitespace { line_offset } => {
 9285                    (*line_offset, line_offset + 1, &layout.space_invisible)
 9286                }
 9287            };
 9288
 9289            let x_offset: ScrollPixelOffset = self.x_for_index(token_offset).into();
 9290            let invisible_offset: ScrollPixelOffset =
 9291                ((layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0)
 9292                    .into();
 9293            let origin = content_origin
 9294                + gpui::point(
 9295                    Pixels::from(
 9296                        x_offset + invisible_offset - layout.position_map.scroll_pixel_position.x,
 9297                    ),
 9298                    line_y,
 9299                );
 9300
 9301            (
 9302                [token_offset, token_end_offset],
 9303                Box::new(move |window: &mut Window, cx: &mut App| {
 9304                    invisible_symbol
 9305                        .paint(origin, line_height, TextAlign::Left, None, window, cx)
 9306                        .log_err();
 9307                }),
 9308            )
 9309        };
 9310
 9311        let invisible_iter = self.invisibles.iter().map(extract_whitespace_info);
 9312        match whitespace_setting {
 9313            ShowWhitespaceSetting::None => (),
 9314            ShowWhitespaceSetting::All => invisible_iter.for_each(|(_, paint)| paint(window, cx)),
 9315            ShowWhitespaceSetting::Selection => invisible_iter.for_each(|([start, _], paint)| {
 9316                let invisible_point = DisplayPoint::new(row, start as u32);
 9317                if !selection_ranges
 9318                    .iter()
 9319                    .any(|region| region.start <= invisible_point && invisible_point < region.end)
 9320                {
 9321                    return;
 9322                }
 9323
 9324                paint(window, cx);
 9325            }),
 9326
 9327            ShowWhitespaceSetting::Trailing => {
 9328                let mut previous_start = self.len;
 9329                for ([start, end], paint) in invisible_iter.rev() {
 9330                    if previous_start != end {
 9331                        break;
 9332                    }
 9333                    previous_start = start;
 9334                    paint(window, cx);
 9335                }
 9336            }
 9337
 9338            // For a whitespace to be on a boundary, any of the following conditions need to be met:
 9339            // - It is a tab
 9340            // - It is adjacent to an edge (start or end)
 9341            // - It is adjacent to a whitespace (left or right)
 9342            ShowWhitespaceSetting::Boundary => {
 9343                // 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
 9344                // the above cases.
 9345                // Note: We zip in the original `invisibles` to check for tab equality
 9346                let mut last_seen: Option<(bool, usize, Box<dyn Fn(&mut Window, &mut App)>)> = None;
 9347                for (([start, end], paint), invisible) in
 9348                    invisible_iter.zip_eq(self.invisibles.iter())
 9349                {
 9350                    let should_render = match (&last_seen, invisible) {
 9351                        (_, Invisible::Tab { .. }) => true,
 9352                        (Some((_, last_end, _)), _) => *last_end == start,
 9353                        _ => false,
 9354                    };
 9355
 9356                    if should_render || start == 0 || end == self.len {
 9357                        paint(window, cx);
 9358
 9359                        // Since we are scanning from the left, we will skip over the first available whitespace that is part
 9360                        // of a boundary between non-whitespace segments, so we correct by manually redrawing it if needed.
 9361                        if let Some((should_render_last, last_end, paint_last)) = last_seen {
 9362                            // Note that we need to make sure that the last one is actually adjacent
 9363                            if !should_render_last && last_end == start {
 9364                                paint_last(window, cx);
 9365                            }
 9366                        }
 9367                    }
 9368
 9369                    // Manually render anything within a selection
 9370                    let invisible_point = DisplayPoint::new(row, start as u32);
 9371                    if selection_ranges.iter().any(|region| {
 9372                        region.start <= invisible_point && invisible_point < region.end
 9373                    }) {
 9374                        paint(window, cx);
 9375                    }
 9376
 9377                    last_seen = Some((should_render, end, paint));
 9378                }
 9379            }
 9380        }
 9381    }
 9382
 9383    pub fn x_for_index(&self, index: usize) -> Pixels {
 9384        let mut fragment_start_x = Pixels::ZERO;
 9385        let mut fragment_start_index = 0;
 9386
 9387        for fragment in &self.fragments {
 9388            match fragment {
 9389                LineFragment::Text(shaped_line) => {
 9390                    let fragment_end_index = fragment_start_index + shaped_line.len;
 9391                    if index < fragment_end_index {
 9392                        return fragment_start_x
 9393                            + shaped_line.x_for_index(index - fragment_start_index);
 9394                    }
 9395                    fragment_start_x += shaped_line.width;
 9396                    fragment_start_index = fragment_end_index;
 9397                }
 9398                LineFragment::Element { len, size, .. } => {
 9399                    let fragment_end_index = fragment_start_index + len;
 9400                    if index < fragment_end_index {
 9401                        return fragment_start_x;
 9402                    }
 9403                    fragment_start_x += size.width;
 9404                    fragment_start_index = fragment_end_index;
 9405                }
 9406            }
 9407        }
 9408
 9409        fragment_start_x
 9410    }
 9411
 9412    pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
 9413        let mut fragment_start_x = Pixels::ZERO;
 9414        let mut fragment_start_index = 0;
 9415
 9416        for fragment in &self.fragments {
 9417            match fragment {
 9418                LineFragment::Text(shaped_line) => {
 9419                    let fragment_end_x = fragment_start_x + shaped_line.width;
 9420                    if x < fragment_end_x {
 9421                        return Some(
 9422                            fragment_start_index + shaped_line.index_for_x(x - fragment_start_x)?,
 9423                        );
 9424                    }
 9425                    fragment_start_x = fragment_end_x;
 9426                    fragment_start_index += shaped_line.len;
 9427                }
 9428                LineFragment::Element { len, size, .. } => {
 9429                    let fragment_end_x = fragment_start_x + size.width;
 9430                    if x < fragment_end_x {
 9431                        return Some(fragment_start_index);
 9432                    }
 9433                    fragment_start_index += len;
 9434                    fragment_start_x = fragment_end_x;
 9435                }
 9436            }
 9437        }
 9438
 9439        None
 9440    }
 9441
 9442    pub fn font_id_for_index(&self, index: usize) -> Option<FontId> {
 9443        let mut fragment_start_index = 0;
 9444
 9445        for fragment in &self.fragments {
 9446            match fragment {
 9447                LineFragment::Text(shaped_line) => {
 9448                    let fragment_end_index = fragment_start_index + shaped_line.len;
 9449                    if index < fragment_end_index {
 9450                        return shaped_line.font_id_for_index(index - fragment_start_index);
 9451                    }
 9452                    fragment_start_index = fragment_end_index;
 9453                }
 9454                LineFragment::Element { len, .. } => {
 9455                    let fragment_end_index = fragment_start_index + len;
 9456                    if index < fragment_end_index {
 9457                        return None;
 9458                    }
 9459                    fragment_start_index = fragment_end_index;
 9460                }
 9461            }
 9462        }
 9463
 9464        None
 9465    }
 9466
 9467    pub fn alignment_offset(&self, text_align: TextAlign, content_width: Pixels) -> Pixels {
 9468        let line_width = self.width;
 9469        match text_align {
 9470            TextAlign::Left => px(0.0),
 9471            TextAlign::Center => (content_width - line_width) / 2.0,
 9472            TextAlign::Right => content_width - line_width,
 9473        }
 9474    }
 9475}
 9476
 9477#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 9478enum Invisible {
 9479    /// A tab character
 9480    ///
 9481    /// A tab character is internally represented by spaces (configured by the user's tab width)
 9482    /// aligned to the nearest column, so it's necessary to store the start and end offset for
 9483    /// adjacency checks.
 9484    Tab {
 9485        line_start_offset: usize,
 9486        line_end_offset: usize,
 9487    },
 9488    Whitespace {
 9489        line_offset: usize,
 9490    },
 9491}
 9492
 9493impl EditorElement {
 9494    /// Returns the rem size to use when rendering the [`EditorElement`].
 9495    ///
 9496    /// This allows UI elements to scale based on the `buffer_font_size`.
 9497    fn rem_size(&self, cx: &mut App) -> Option<Pixels> {
 9498        match self.editor.read(cx).mode {
 9499            EditorMode::Full {
 9500                scale_ui_elements_with_buffer_font_size: true,
 9501                ..
 9502            }
 9503            | EditorMode::Minimap { .. } => {
 9504                let buffer_font_size = self.style.text.font_size;
 9505                match buffer_font_size {
 9506                    AbsoluteLength::Pixels(pixels) => {
 9507                        let rem_size_scale = {
 9508                            // Our default UI font size is 14px on a 16px base scale.
 9509                            // This means the default UI font size is 0.875rems.
 9510                            let default_font_size_scale = 14. / ui::BASE_REM_SIZE_IN_PX;
 9511
 9512                            // We then determine the delta between a single rem and the default font
 9513                            // size scale.
 9514                            let default_font_size_delta = 1. - default_font_size_scale;
 9515
 9516                            // Finally, we add this delta to 1rem to get the scale factor that
 9517                            // should be used to scale up the UI.
 9518                            1. + default_font_size_delta
 9519                        };
 9520
 9521                        Some(pixels * rem_size_scale)
 9522                    }
 9523                    AbsoluteLength::Rems(rems) => {
 9524                        Some(rems.to_pixels(ui::BASE_REM_SIZE_IN_PX.into()))
 9525                    }
 9526                }
 9527            }
 9528            // We currently use single-line and auto-height editors in UI contexts,
 9529            // so we don't want to scale everything with the buffer font size, as it
 9530            // ends up looking off.
 9531            _ => None,
 9532        }
 9533    }
 9534
 9535    fn editor_with_selections(&self, cx: &App) -> Option<Entity<Editor>> {
 9536        if let EditorMode::Minimap { parent } = self.editor.read(cx).mode() {
 9537            parent.upgrade()
 9538        } else {
 9539            Some(self.editor.clone())
 9540        }
 9541    }
 9542}
 9543
 9544#[derive(Default)]
 9545pub struct EditorRequestLayoutState {
 9546    // We use prepaint depth to limit the number of times prepaint is
 9547    // called recursively. We need this so that we can update stale
 9548    // data for e.g. block heights in block map.
 9549    prepaint_depth: Rc<Cell<usize>>,
 9550}
 9551
 9552impl EditorRequestLayoutState {
 9553    // In ideal conditions we only need one more subsequent prepaint call for resize to take effect.
 9554    // i.e. MAX_PREPAINT_DEPTH = 2, but since moving blocks inline (place_near), more lines from
 9555    // below get exposed, and we end up querying blocks for those lines too in subsequent renders.
 9556    // Setting MAX_PREPAINT_DEPTH = 3, passes all tests. Just to be on the safe side we set it to 5, so
 9557    // that subsequent shrinking does not lead to incorrect block placing.
 9558    const MAX_PREPAINT_DEPTH: usize = 5;
 9559
 9560    fn increment_prepaint_depth(&self) -> EditorPrepaintGuard {
 9561        let depth = self.prepaint_depth.get();
 9562        self.prepaint_depth.set(depth + 1);
 9563        EditorPrepaintGuard {
 9564            prepaint_depth: self.prepaint_depth.clone(),
 9565        }
 9566    }
 9567
 9568    fn has_remaining_prepaint_depth(&self) -> bool {
 9569        self.prepaint_depth.get() < Self::MAX_PREPAINT_DEPTH
 9570    }
 9571}
 9572
 9573struct EditorPrepaintGuard {
 9574    prepaint_depth: Rc<Cell<usize>>,
 9575}
 9576
 9577impl Drop for EditorPrepaintGuard {
 9578    fn drop(&mut self) {
 9579        let depth = self.prepaint_depth.get();
 9580        self.prepaint_depth.set(depth.saturating_sub(1));
 9581    }
 9582}
 9583
 9584impl Element for EditorElement {
 9585    type RequestLayoutState = EditorRequestLayoutState;
 9586    type PrepaintState = EditorLayout;
 9587
 9588    fn id(&self) -> Option<ElementId> {
 9589        None
 9590    }
 9591
 9592    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
 9593        None
 9594    }
 9595
 9596    fn request_layout(
 9597        &mut self,
 9598        _: Option<&GlobalElementId>,
 9599        _inspector_id: Option<&gpui::InspectorElementId>,
 9600        window: &mut Window,
 9601        cx: &mut App,
 9602    ) -> (gpui::LayoutId, Self::RequestLayoutState) {
 9603        let rem_size = self.rem_size(cx);
 9604        window.with_rem_size(rem_size, |window| {
 9605            self.editor.update(cx, |editor, cx| {
 9606                editor.set_style(self.style.clone(), window, cx);
 9607
 9608                let layout_id = match editor.mode {
 9609                    EditorMode::SingleLine => {
 9610                        let rem_size = window.rem_size();
 9611                        let height = self.style.text.line_height_in_pixels(rem_size);
 9612                        let mut style = Style::default();
 9613                        style.size.height = height.into();
 9614                        style.size.width = relative(1.).into();
 9615                        window.request_layout(style, None, cx)
 9616                    }
 9617                    EditorMode::AutoHeight {
 9618                        min_lines,
 9619                        max_lines,
 9620                    } => {
 9621                        let editor_handle = cx.entity();
 9622                        window.request_measured_layout(
 9623                            Style::default(),
 9624                            move |known_dimensions, available_space, window, cx| {
 9625                                editor_handle
 9626                                    .update(cx, |editor, cx| {
 9627                                        compute_auto_height_layout(
 9628                                            editor,
 9629                                            min_lines,
 9630                                            max_lines,
 9631                                            known_dimensions,
 9632                                            available_space.width,
 9633                                            window,
 9634                                            cx,
 9635                                        )
 9636                                    })
 9637                                    .unwrap_or_default()
 9638                            },
 9639                        )
 9640                    }
 9641                    EditorMode::Minimap { .. } => {
 9642                        let mut style = Style::default();
 9643                        style.size.width = relative(1.).into();
 9644                        style.size.height = relative(1.).into();
 9645                        window.request_layout(style, None, cx)
 9646                    }
 9647                    EditorMode::Full {
 9648                        sizing_behavior, ..
 9649                    } => {
 9650                        let mut style = Style::default();
 9651                        style.size.width = relative(1.).into();
 9652                        if sizing_behavior == SizingBehavior::SizeByContent {
 9653                            let snapshot = editor.snapshot(window, cx);
 9654                            let line_height =
 9655                                self.style.text.line_height_in_pixels(window.rem_size());
 9656                            let scroll_height =
 9657                                (snapshot.max_point().row().next_row().0 as f32) * line_height;
 9658                            style.size.height = scroll_height.into();
 9659                        } else {
 9660                            style.size.height = relative(1.).into();
 9661                        }
 9662                        window.request_layout(style, None, cx)
 9663                    }
 9664                };
 9665
 9666                (layout_id, EditorRequestLayoutState::default())
 9667            })
 9668        })
 9669    }
 9670
 9671    fn prepaint(
 9672        &mut self,
 9673        _: Option<&GlobalElementId>,
 9674        _inspector_id: Option<&gpui::InspectorElementId>,
 9675        bounds: Bounds<Pixels>,
 9676        request_layout: &mut Self::RequestLayoutState,
 9677        window: &mut Window,
 9678        cx: &mut App,
 9679    ) -> Self::PrepaintState {
 9680        let _prepaint_depth_guard = request_layout.increment_prepaint_depth();
 9681        let text_style = TextStyleRefinement {
 9682            font_size: Some(self.style.text.font_size),
 9683            line_height: Some(self.style.text.line_height),
 9684            ..Default::default()
 9685        };
 9686
 9687        let is_minimap = self.editor.read(cx).mode.is_minimap();
 9688        let is_singleton = self.editor.read(cx).buffer_kind(cx) == ItemBufferKind::Singleton;
 9689
 9690        if !is_minimap {
 9691            let focus_handle = self.editor.focus_handle(cx);
 9692            window.set_view_id(self.editor.entity_id());
 9693            window.set_focus_handle(&focus_handle, cx);
 9694        }
 9695
 9696        let rem_size = self.rem_size(cx);
 9697        window.with_rem_size(rem_size, |window| {
 9698            window.with_text_style(Some(text_style), |window| {
 9699                window.with_content_mask(Some(ContentMask { bounds }), |window| {
 9700                    let (mut snapshot, is_read_only) = self.editor.update(cx, |editor, cx| {
 9701                        (editor.snapshot(window, cx), editor.read_only(cx))
 9702                    });
 9703                    let style = &self.style;
 9704
 9705                    let rem_size = window.rem_size();
 9706                    let font_id = window.text_system().resolve_font(&style.text.font());
 9707                    let font_size = style.text.font_size.to_pixels(rem_size);
 9708                    let line_height = style.text.line_height_in_pixels(rem_size);
 9709                    let em_width = window.text_system().em_width(font_id, font_size).unwrap();
 9710                    let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
 9711                    let em_layout_width = window.text_system().em_layout_width(font_id, font_size);
 9712                    let glyph_grid_cell = size(em_advance, line_height);
 9713
 9714                    let gutter_dimensions =
 9715                        snapshot.gutter_dimensions(font_id, font_size, style, window, cx);
 9716                    let text_width = bounds.size.width - gutter_dimensions.width;
 9717
 9718                    let settings = EditorSettings::get_global(cx);
 9719                    let scrollbars_shown = settings.scrollbar.show != ShowScrollbar::Never;
 9720                    let vertical_scrollbar_width = (scrollbars_shown
 9721                        && settings.scrollbar.axes.vertical
 9722                        && self.editor.read(cx).show_scrollbars.vertical)
 9723                        .then_some(style.scrollbar_width)
 9724                        .unwrap_or_default();
 9725                    let minimap_width = self
 9726                        .get_minimap_width(
 9727                            &settings.minimap,
 9728                            scrollbars_shown,
 9729                            text_width,
 9730                            em_width,
 9731                            font_size,
 9732                            rem_size,
 9733                            cx,
 9734                        )
 9735                        .unwrap_or_default();
 9736
 9737                    let right_margin = minimap_width + vertical_scrollbar_width;
 9738
 9739                    let extended_right = 2 * em_width + right_margin;
 9740                    let editor_width = text_width - gutter_dimensions.margin - extended_right;
 9741                    let editor_margins = EditorMargins {
 9742                        gutter: gutter_dimensions,
 9743                        right: right_margin,
 9744                        extended_right,
 9745                    };
 9746
 9747                    snapshot = self.editor.update(cx, |editor, cx| {
 9748                        editor.last_bounds = Some(bounds);
 9749                        editor.gutter_dimensions = gutter_dimensions;
 9750                        editor.set_visible_line_count(
 9751                            (bounds.size.height / line_height) as f64,
 9752                            window,
 9753                            cx,
 9754                        );
 9755                        editor.set_visible_column_count(f64::from(editor_width / em_advance));
 9756
 9757                        if matches!(
 9758                            editor.mode,
 9759                            EditorMode::AutoHeight { .. } | EditorMode::Minimap { .. }
 9760                        ) {
 9761                            snapshot
 9762                        } else {
 9763                            let wrap_width = calculate_wrap_width(
 9764                                editor.soft_wrap_mode(cx),
 9765                                editor_width,
 9766                                em_layout_width,
 9767                            );
 9768
 9769                            if editor.set_wrap_width(wrap_width, cx) {
 9770                                editor.snapshot(window, cx)
 9771                            } else {
 9772                                snapshot
 9773                            }
 9774                        }
 9775                    });
 9776
 9777                    let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
 9778                    let gutter_hitbox = window.insert_hitbox(
 9779                        gutter_bounds(bounds, gutter_dimensions),
 9780                        HitboxBehavior::Normal,
 9781                    );
 9782                    let text_hitbox = window.insert_hitbox(
 9783                        Bounds {
 9784                            origin: gutter_hitbox.top_right(),
 9785                            size: size(text_width, bounds.size.height),
 9786                        },
 9787                        HitboxBehavior::Normal,
 9788                    );
 9789
 9790                    // Offset the content_bounds from the text_bounds by the gutter margin (which
 9791                    // is roughly half a character wide) to make hit testing work more like how we want.
 9792                    let content_offset = point(editor_margins.gutter.margin, Pixels::ZERO);
 9793                    let content_origin = text_hitbox.origin + content_offset;
 9794
 9795                    let height_in_lines = f64::from(bounds.size.height / line_height);
 9796                    let max_row = snapshot.max_point().row().as_f64();
 9797
 9798                    // Calculate how much of the editor is clipped by parent containers (e.g., List).
 9799                    // This allows us to only render lines that are actually visible, which is
 9800                    // critical for performance when large AutoHeight editors are inside Lists.
 9801                    let visible_bounds = window.content_mask().bounds;
 9802                    let clipped_top = (visible_bounds.origin.y - bounds.origin.y).max(px(0.));
 9803                    let clipped_top_in_lines = f64::from(clipped_top / line_height);
 9804                    let visible_height_in_lines =
 9805                        f64::from(visible_bounds.size.height / line_height);
 9806
 9807                    // The max scroll position for the top of the window
 9808                    let scroll_beyond_last_line = self.editor.read(cx).scroll_beyond_last_line(cx);
 9809                    let max_scroll_top = match scroll_beyond_last_line {
 9810                        ScrollBeyondLastLine::OnePage => max_row,
 9811                        ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.).max(0.),
 9812                        ScrollBeyondLastLine::VerticalScrollMargin => {
 9813                            let settings = EditorSettings::get_global(cx);
 9814                            (max_row - height_in_lines + 1. + settings.vertical_scroll_margin)
 9815                                .max(0.)
 9816                        }
 9817                    };
 9818
 9819                    let (
 9820                        autoscroll_request,
 9821                        autoscroll_containing_element,
 9822                        needs_horizontal_autoscroll,
 9823                    ) = self.editor.update(cx, |editor, cx| {
 9824                        let autoscroll_request = editor.scroll_manager.take_autoscroll_request();
 9825
 9826                        let autoscroll_containing_element =
 9827                            autoscroll_request.is_some() || editor.has_pending_selection();
 9828
 9829                        let (needs_horizontal_autoscroll, was_scrolled) = editor
 9830                            .autoscroll_vertically(
 9831                                bounds,
 9832                                line_height,
 9833                                max_scroll_top,
 9834                                autoscroll_request,
 9835                                window,
 9836                                cx,
 9837                            );
 9838                        if was_scrolled.0 {
 9839                            snapshot = editor.snapshot(window, cx);
 9840                        }
 9841                        (
 9842                            autoscroll_request,
 9843                            autoscroll_containing_element,
 9844                            needs_horizontal_autoscroll,
 9845                        )
 9846                    });
 9847
 9848                    let mut scroll_position = snapshot.scroll_position();
 9849                    // The scroll position is a fractional point, the whole number of which represents
 9850                    // the top of the window in terms of display rows.
 9851                    // We add clipped_top_in_lines to skip rows that are clipped by parent containers,
 9852                    // but we don't modify scroll_position itself since the parent handles positioning.
 9853                    let max_row = snapshot.max_point().row();
 9854                    let start_row = cmp::min(
 9855                        DisplayRow((scroll_position.y + clipped_top_in_lines).floor() as u32),
 9856                        max_row,
 9857                    );
 9858                    let end_row = cmp::min(
 9859                        (scroll_position.y + clipped_top_in_lines + visible_height_in_lines).ceil()
 9860                            as u32,
 9861                        max_row.next_row().0,
 9862                    );
 9863                    let end_row = DisplayRow(end_row);
 9864
 9865                    let row_infos = snapshot // note we only get the visual range
 9866                        .row_infos(start_row)
 9867                        .take((start_row..end_row).len())
 9868                        .collect::<Vec<RowInfo>>();
 9869                    let is_row_soft_wrapped = |row: usize| {
 9870                        row_infos
 9871                            .get(row)
 9872                            .is_none_or(|info| info.buffer_row.is_none())
 9873                    };
 9874
 9875                    let start_anchor = if start_row == Default::default() {
 9876                        Anchor::Min
 9877                    } else {
 9878                        snapshot.buffer_snapshot().anchor_before(
 9879                            DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left),
 9880                        )
 9881                    };
 9882                    let end_anchor = if end_row > max_row {
 9883                        Anchor::Max
 9884                    } else {
 9885                        snapshot.buffer_snapshot().anchor_before(
 9886                            DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right),
 9887                        )
 9888                    };
 9889
 9890                    let mut highlighted_rows = self
 9891                        .editor
 9892                        .update(cx, |editor, cx| editor.highlighted_display_rows(window, cx));
 9893
 9894                    let is_light = cx.theme().appearance().is_light();
 9895
 9896                    let mut highlighted_ranges = self
 9897                        .editor_with_selections(cx)
 9898                        .map(|editor| {
 9899                            if editor == self.editor {
 9900                                editor.read(cx).background_highlights_in_range(
 9901                                    start_anchor..end_anchor,
 9902                                    &snapshot.display_snapshot,
 9903                                    cx.theme(),
 9904                                )
 9905                            } else {
 9906                                editor.update(cx, |editor, cx| {
 9907                                    let snapshot = editor.snapshot(window, cx);
 9908                                    let start_anchor = if start_row == Default::default() {
 9909                                        Anchor::Min
 9910                                    } else {
 9911                                        snapshot.buffer_snapshot().anchor_before(
 9912                                            DisplayPoint::new(start_row, 0)
 9913                                                .to_offset(&snapshot, Bias::Left),
 9914                                        )
 9915                                    };
 9916                                    let end_anchor = if end_row > max_row {
 9917                                        Anchor::Max
 9918                                    } else {
 9919                                        snapshot.buffer_snapshot().anchor_before(
 9920                                            DisplayPoint::new(end_row, 0)
 9921                                                .to_offset(&snapshot, Bias::Right),
 9922                                        )
 9923                                    };
 9924
 9925                                    editor.background_highlights_in_range(
 9926                                        start_anchor..end_anchor,
 9927                                        &snapshot.display_snapshot,
 9928                                        cx.theme(),
 9929                                    )
 9930                                })
 9931                            }
 9932                        })
 9933                        .unwrap_or_default();
 9934
 9935                    for (ix, row_info) in row_infos.iter().enumerate() {
 9936                        let Some(diff_status) = row_info.diff_status else {
 9937                            continue;
 9938                        };
 9939
 9940                        let background_color = match diff_status.kind {
 9941                            DiffHunkStatusKind::Added => cx.theme().colors().version_control_added,
 9942                            DiffHunkStatusKind::Deleted => {
 9943                                cx.theme().colors().version_control_deleted
 9944                            }
 9945                            DiffHunkStatusKind::Modified => {
 9946                                debug_panic!("modified diff status for row info");
 9947                                continue;
 9948                            }
 9949                        };
 9950
 9951                        let hunk_opacity = if is_light { 0.16 } else { 0.12 };
 9952
 9953                        let hollow_highlight = LineHighlight {
 9954                            background: (background_color.opacity(if is_light {
 9955                                0.08
 9956                            } else {
 9957                                0.06
 9958                            }))
 9959                            .into(),
 9960                            border: Some(if is_light {
 9961                                background_color.opacity(0.48)
 9962                            } else {
 9963                                background_color.opacity(0.36)
 9964                            }),
 9965                            include_gutter: true,
 9966                            type_id: None,
 9967                        };
 9968
 9969                        let filled_highlight = LineHighlight {
 9970                            background: solid_background(background_color.opacity(hunk_opacity)),
 9971                            border: None,
 9972                            include_gutter: true,
 9973                            type_id: None,
 9974                        };
 9975
 9976                        let background = if Self::diff_hunk_hollow(diff_status, cx) {
 9977                            hollow_highlight
 9978                        } else {
 9979                            filled_highlight
 9980                        };
 9981
 9982                        let base_display_point =
 9983                            DisplayPoint::new(start_row + DisplayRow(ix as u32), 0);
 9984
 9985                        highlighted_rows
 9986                            .entry(base_display_point.row())
 9987                            .or_insert(background);
 9988                    }
 9989
 9990                    // Add diff review drag selection highlight to text area
 9991                    if let Some(drag_state) = &self.editor.read(cx).diff_review_drag_state {
 9992                        let range = drag_state.row_range(&snapshot.display_snapshot);
 9993                        let start_row = range.start().0;
 9994                        let end_row = range.end().0;
 9995                        let drag_highlight_color =
 9996                            cx.theme().colors().editor_active_line_background;
 9997                        let drag_highlight = LineHighlight {
 9998                            background: solid_background(drag_highlight_color),
 9999                            border: Some(cx.theme().colors().border_focused),
10000                            include_gutter: true,
10001                            type_id: None,
10002                        };
10003                        for row_num in start_row..=end_row {
10004                            highlighted_rows
10005                                .entry(DisplayRow(row_num))
10006                                .or_insert(drag_highlight);
10007                        }
10008                    }
10009
10010                    let highlighted_gutter_ranges =
10011                        self.editor.read(cx).gutter_highlights_in_range(
10012                            start_anchor..end_anchor,
10013                            &snapshot.display_snapshot,
10014                            cx,
10015                        );
10016
10017                    let document_colors = self
10018                        .editor
10019                        .read(cx)
10020                        .colors
10021                        .as_ref()
10022                        .map(|colors| colors.editor_display_highlights(&snapshot));
10023                    let redacted_ranges = self.editor.read(cx).redacted_ranges(
10024                        start_anchor..end_anchor,
10025                        &snapshot.display_snapshot,
10026                        cx,
10027                    );
10028
10029                    let (local_selections, selected_buffer_ids, latest_selection_anchors): (
10030                        Vec<Selection<Point>>,
10031                        Vec<BufferId>,
10032                        HashMap<BufferId, Anchor>,
10033                    ) = self
10034                        .editor_with_selections(cx)
10035                        .map(|editor| {
10036                            editor.update(cx, |editor, cx| {
10037                                let all_selections =
10038                                    editor.selections.all::<Point>(&snapshot.display_snapshot);
10039                                let all_anchor_selections =
10040                                    editor.selections.all_anchors(&snapshot.display_snapshot);
10041                                let selected_buffer_ids =
10042                                    if editor.buffer_kind(cx) == ItemBufferKind::Singleton {
10043                                        Vec::new()
10044                                    } else {
10045                                        let mut selected_buffer_ids =
10046                                            Vec::with_capacity(all_selections.len());
10047
10048                                        for selection in all_selections {
10049                                            for buffer_id in snapshot
10050                                                .buffer_snapshot()
10051                                                .buffer_ids_for_range(selection.range())
10052                                            {
10053                                                if selected_buffer_ids.last() != Some(&buffer_id) {
10054                                                    selected_buffer_ids.push(buffer_id);
10055                                                }
10056                                            }
10057                                        }
10058
10059                                        selected_buffer_ids
10060                                    };
10061
10062                                let mut selections = editor.selections.disjoint_in_range(
10063                                    start_anchor..end_anchor,
10064                                    &snapshot.display_snapshot,
10065                                );
10066                                selections
10067                                    .extend(editor.selections.pending(&snapshot.display_snapshot));
10068
10069                                let mut anchors_by_buffer: HashMap<BufferId, (usize, Anchor)> =
10070                                    HashMap::default();
10071                                for selection in all_anchor_selections.iter() {
10072                                    let head = selection.head();
10073                                    if let Some((text_anchor, _)) =
10074                                        snapshot.buffer_snapshot().anchor_to_buffer_anchor(head)
10075                                    {
10076                                        anchors_by_buffer
10077                                            .entry(text_anchor.buffer_id)
10078                                            .and_modify(|(latest_id, latest_anchor)| {
10079                                                if selection.id > *latest_id {
10080                                                    *latest_id = selection.id;
10081                                                    *latest_anchor = head;
10082                                                }
10083                                            })
10084                                            .or_insert((selection.id, head));
10085                                    }
10086                                }
10087                                let latest_selection_anchors = anchors_by_buffer
10088                                    .into_iter()
10089                                    .map(|(buffer_id, (_, anchor))| (buffer_id, anchor))
10090                                    .collect();
10091
10092                                (selections, selected_buffer_ids, latest_selection_anchors)
10093                            })
10094                        })
10095                        .unwrap_or_else(|| (Vec::new(), Vec::new(), HashMap::default()));
10096
10097                    let (selections, mut active_rows, newest_selection_head) = self
10098                        .layout_selections(
10099                            start_anchor,
10100                            end_anchor,
10101                            &local_selections,
10102                            &snapshot,
10103                            start_row,
10104                            end_row,
10105                            window,
10106                            cx,
10107                        );
10108
10109                    // relative rows are based on newest selection, even outside the visible area
10110                    let current_selection_head = self.editor.update(cx, |editor, cx| {
10111                        (editor.selections.count() != 0).then(|| {
10112                            let newest = editor
10113                                .selections
10114                                .newest::<Point>(&editor.display_snapshot(cx));
10115
10116                            SelectionLayout::new(
10117                                newest,
10118                                editor.selections.line_mode(),
10119                                editor.cursor_offset_on_selection,
10120                                editor.cursor_shape,
10121                                &snapshot,
10122                                true,
10123                                true,
10124                                None,
10125                            )
10126                            .head
10127                            .row()
10128                        })
10129                    });
10130
10131                    let run_indicator_rows = self.editor.update(cx, |editor, cx| {
10132                        editor.active_run_indicators(start_row..end_row, window, cx)
10133                    });
10134
10135                    let mut breakpoint_rows = self.editor.update(cx, |editor, cx| {
10136                        editor.active_breakpoints(start_row..end_row, window, cx)
10137                    });
10138
10139                    for (display_row, (_, bp, state)) in &breakpoint_rows {
10140                        if bp.is_enabled() && state.is_none_or(|s| s.verified) {
10141                            active_rows.entry(*display_row).or_default().breakpoint = true;
10142                        }
10143                    }
10144
10145                    let gutter = Gutter {
10146                        line_height,
10147                        range: start_row..end_row,
10148                        scroll_position,
10149                        dimensions: &gutter_dimensions,
10150                        hitbox: &gutter_hitbox,
10151                        snapshot: &snapshot,
10152                        row_infos: &row_infos,
10153                    };
10154
10155                    let line_numbers = self.layout_line_numbers(
10156                        &gutter,
10157                        &active_rows,
10158                        current_selection_head,
10159                        window,
10160                        cx,
10161                    );
10162
10163                    let mut expand_toggles =
10164                        window.with_element_namespace("expand_toggles", |window| {
10165                            self.layout_expand_toggles(
10166                                &gutter_hitbox,
10167                                gutter_dimensions,
10168                                em_width,
10169                                line_height,
10170                                scroll_position,
10171                                &row_infos,
10172                                window,
10173                                cx,
10174                            )
10175                        });
10176
10177                    let mut crease_toggles =
10178                        window.with_element_namespace("crease_toggles", |window| {
10179                            self.layout_crease_toggles(
10180                                start_row..end_row,
10181                                &row_infos,
10182                                &active_rows,
10183                                &snapshot,
10184                                window,
10185                                cx,
10186                            )
10187                        });
10188                    let crease_trailers =
10189                        window.with_element_namespace("crease_trailers", |window| {
10190                            self.layout_crease_trailers(
10191                                row_infos.iter().cloned(),
10192                                &snapshot,
10193                                window,
10194                                cx,
10195                            )
10196                        });
10197
10198                    let display_hunks = self.layout_gutter_diff_hunks(
10199                        line_height,
10200                        &gutter_hitbox,
10201                        start_row..end_row,
10202                        &snapshot,
10203                        window,
10204                        cx,
10205                    );
10206
10207                    Self::layout_word_diff_highlights(
10208                        &display_hunks,
10209                        &row_infos,
10210                        start_row,
10211                        &snapshot,
10212                        &mut highlighted_ranges,
10213                        cx,
10214                    );
10215
10216                    let merged_highlighted_ranges =
10217                        if let Some((_, colors)) = document_colors.as_ref() {
10218                            &highlighted_ranges
10219                                .clone()
10220                                .into_iter()
10221                                .chain(colors.clone())
10222                                .collect()
10223                        } else {
10224                            &highlighted_ranges
10225                        };
10226                    let bg_segments_per_row = Self::bg_segments_per_row(
10227                        start_row..end_row,
10228                        &selections,
10229                        &merged_highlighted_ranges,
10230                        self.style.background,
10231                    );
10232
10233                    let mut line_layouts = Self::layout_lines(
10234                        start_row..end_row,
10235                        &snapshot,
10236                        &self.style,
10237                        editor_width,
10238                        is_row_soft_wrapped,
10239                        &bg_segments_per_row,
10240                        window,
10241                        cx,
10242                    );
10243                    let new_renderer_widths = (!is_minimap).then(|| {
10244                        line_layouts
10245                            .iter()
10246                            .flat_map(|layout| &layout.fragments)
10247                            .filter_map(|fragment| {
10248                                if let LineFragment::Element { id, size, .. } = fragment {
10249                                    Some((*id, size.width))
10250                                } else {
10251                                    None
10252                                }
10253                            })
10254                    });
10255                    let renderer_widths_changed = request_layout.has_remaining_prepaint_depth()
10256                        && new_renderer_widths.is_some_and(|new_renderer_widths| {
10257                            self.editor.update(cx, |editor, cx| {
10258                                editor.update_renderer_widths(new_renderer_widths, cx)
10259                            })
10260                        });
10261                    if renderer_widths_changed {
10262                        return self.prepaint(
10263                            None,
10264                            _inspector_id,
10265                            bounds,
10266                            request_layout,
10267                            window,
10268                            cx,
10269                        );
10270                    }
10271
10272                    let longest_line_blame_width = self
10273                        .editor
10274                        .update(cx, |editor, cx| {
10275                            if !editor.show_git_blame_inline {
10276                                return None;
10277                            }
10278                            let blame = editor.blame.as_ref()?;
10279                            let (_, blame_entry) = blame
10280                                .update(cx, |blame, cx| {
10281                                    let row_infos =
10282                                        snapshot.row_infos(snapshot.longest_row()).next()?;
10283                                    blame.blame_for_rows(&[row_infos], cx).next()
10284                                })
10285                                .flatten()?;
10286                            let mut element = render_inline_blame_entry(blame_entry, style, cx)?;
10287                            let inline_blame_padding =
10288                                ProjectSettings::get_global(cx).git.inline_blame.padding as f32
10289                                    * em_advance;
10290                            Some(
10291                                element
10292                                    .layout_as_root(AvailableSpace::min_size(), window, cx)
10293                                    .width
10294                                    + inline_blame_padding,
10295                            )
10296                        })
10297                        .unwrap_or(Pixels::ZERO);
10298
10299                    let longest_line_width = layout_line(
10300                        snapshot.longest_row(),
10301                        &snapshot,
10302                        style,
10303                        editor_width,
10304                        is_row_soft_wrapped,
10305                        window,
10306                        cx,
10307                    )
10308                    .width;
10309
10310                    let scrollbar_layout_information = ScrollbarLayoutInformation::new(
10311                        text_hitbox.bounds,
10312                        glyph_grid_cell,
10313                        size(
10314                            longest_line_width,
10315                            Pixels::from(max_row.as_f64() * f64::from(line_height)),
10316                        ),
10317                        longest_line_blame_width,
10318                        EditorSettings::get_global(cx),
10319                        scroll_beyond_last_line,
10320                    );
10321
10322                    let mut scroll_width = scrollbar_layout_information.scroll_range.width;
10323
10324                    let sticky_header_excerpt = if snapshot.buffer_snapshot().show_headers() {
10325                        snapshot.sticky_header_excerpt(scroll_position.y)
10326                    } else {
10327                        None
10328                    };
10329                    let sticky_header_excerpt_id = sticky_header_excerpt
10330                        .as_ref()
10331                        .map(|top| top.excerpt.buffer_id());
10332
10333                    let buffer = snapshot.buffer_snapshot();
10334                    let start_buffer_row = MultiBufferRow(start_anchor.to_point(&buffer).row);
10335                    let end_buffer_row = MultiBufferRow(end_anchor.to_point(&buffer).row);
10336
10337                    let preliminary_scroll_pixel_position = point(
10338                        scroll_position.x * f64::from(em_layout_width),
10339                        scroll_position.y * f64::from(line_height),
10340                    );
10341                    let indent_guides = self.layout_indent_guides(
10342                        content_origin,
10343                        text_hitbox.origin,
10344                        start_buffer_row..end_buffer_row,
10345                        preliminary_scroll_pixel_position,
10346                        line_height,
10347                        &snapshot,
10348                        window,
10349                        cx,
10350                    );
10351                    let indent_guides_for_spacers = indent_guides.clone();
10352
10353                    let blocks = (!is_minimap)
10354                        .then(|| {
10355                            window.with_element_namespace("blocks", |window| {
10356                                self.render_blocks(
10357                                    start_row..end_row,
10358                                    &snapshot,
10359                                    &hitbox,
10360                                    &text_hitbox,
10361                                    editor_width,
10362                                    &mut scroll_width,
10363                                    &editor_margins,
10364                                    em_width,
10365                                    gutter_dimensions.full_width(),
10366                                    line_height,
10367                                    &mut line_layouts,
10368                                    &local_selections,
10369                                    &selected_buffer_ids,
10370                                    &latest_selection_anchors,
10371                                    is_row_soft_wrapped,
10372                                    sticky_header_excerpt_id,
10373                                    &indent_guides_for_spacers,
10374                                    window,
10375                                    cx,
10376                                )
10377                            })
10378                        })
10379                        .unwrap_or_default();
10380                    let RenderBlocksOutput {
10381                        non_spacer_blocks: mut blocks,
10382                        mut spacer_blocks,
10383                        row_block_types,
10384                        resized_blocks,
10385                    } = blocks;
10386                    if let Some(resized_blocks) = resized_blocks {
10387                        if request_layout.has_remaining_prepaint_depth() {
10388                            self.editor.update(cx, |editor, cx| {
10389                                editor.resize_blocks(
10390                                    resized_blocks,
10391                                    autoscroll_request.map(|(autoscroll, _)| autoscroll),
10392                                    cx,
10393                                )
10394                            });
10395                            return self.prepaint(
10396                                None,
10397                                _inspector_id,
10398                                bounds,
10399                                request_layout,
10400                                window,
10401                                cx,
10402                            );
10403                        } else {
10404                            debug_panic!(
10405                                "dropping block resize because prepaint depth \
10406                                 limit was reached"
10407                            );
10408                        }
10409                    }
10410
10411                    let sticky_buffer_header = if self.should_show_buffer_headers() {
10412                        sticky_header_excerpt.map(|sticky_header_excerpt| {
10413                            window.with_element_namespace("blocks", |window| {
10414                                self.layout_sticky_buffer_header(
10415                                    sticky_header_excerpt,
10416                                    scroll_position,
10417                                    line_height,
10418                                    right_margin,
10419                                    &snapshot,
10420                                    &hitbox,
10421                                    &selected_buffer_ids,
10422                                    &blocks,
10423                                    &latest_selection_anchors,
10424                                    window,
10425                                    cx,
10426                                )
10427                            })
10428                        })
10429                    } else {
10430                        None
10431                    };
10432
10433                    let scroll_max: gpui::Point<ScrollPixelOffset> = point(
10434                        ScrollPixelOffset::from(
10435                            ((scroll_width - editor_width) / em_layout_width).max(0.0),
10436                        ),
10437                        max_scroll_top,
10438                    );
10439
10440                    self.editor.update(cx, |editor, cx| {
10441                        if editor.scroll_manager.clamp_scroll_left(scroll_max.x, cx) {
10442                            scroll_position.x = scroll_max.x.min(scroll_position.x);
10443                        }
10444
10445                        if needs_horizontal_autoscroll.0
10446                            && let Some(new_scroll_position) = editor.autoscroll_horizontally(
10447                                start_row,
10448                                editor_width,
10449                                scroll_width,
10450                                em_advance,
10451                                &line_layouts,
10452                                autoscroll_request,
10453                                window,
10454                                cx,
10455                            )
10456                        {
10457                            scroll_position = new_scroll_position;
10458                        }
10459                    });
10460
10461                    let scroll_pixel_position = point(
10462                        scroll_position.x * f64::from(em_layout_width),
10463                        scroll_position.y * f64::from(line_height),
10464                    );
10465                    let sticky_headers = if !is_minimap
10466                        && is_singleton
10467                        && EditorSettings::get_global(cx).sticky_scroll.enabled
10468                    {
10469                        let relative = self.editor.read(cx).relative_line_numbers(cx);
10470                        self.layout_sticky_headers(
10471                            &snapshot,
10472                            editor_width,
10473                            is_row_soft_wrapped,
10474                            line_height,
10475                            scroll_pixel_position,
10476                            content_origin,
10477                            &gutter_dimensions,
10478                            &gutter_hitbox,
10479                            &text_hitbox,
10480                            relative,
10481                            current_selection_head,
10482                            window,
10483                            cx,
10484                        )
10485                    } else {
10486                        None
10487                    };
10488                    let indent_guides =
10489                        if scroll_pixel_position != preliminary_scroll_pixel_position {
10490                            self.layout_indent_guides(
10491                                content_origin,
10492                                text_hitbox.origin,
10493                                start_buffer_row..end_buffer_row,
10494                                scroll_pixel_position,
10495                                line_height,
10496                                &snapshot,
10497                                window,
10498                                cx,
10499                            )
10500                        } else {
10501                            indent_guides
10502                        };
10503
10504                    let crease_trailers =
10505                        window.with_element_namespace("crease_trailers", |window| {
10506                            self.prepaint_crease_trailers(
10507                                crease_trailers,
10508                                &line_layouts,
10509                                line_height,
10510                                content_origin,
10511                                scroll_pixel_position,
10512                                em_width,
10513                                window,
10514                                cx,
10515                            )
10516                        });
10517
10518                    let (edit_prediction_popover, edit_prediction_popover_origin) = self
10519                        .editor
10520                        .update(cx, |editor, cx| {
10521                            editor.render_edit_prediction_popover(
10522                                &text_hitbox.bounds,
10523                                content_origin,
10524                                right_margin,
10525                                &snapshot,
10526                                start_row..end_row,
10527                                scroll_position.y,
10528                                scroll_position.y + height_in_lines,
10529                                &line_layouts,
10530                                line_height,
10531                                scroll_position,
10532                                scroll_pixel_position,
10533                                newest_selection_head,
10534                                editor_width,
10535                                style,
10536                                window,
10537                                cx,
10538                            )
10539                        })
10540                        .unzip();
10541
10542                    let mut inline_diagnostics = self.layout_inline_diagnostics(
10543                        &line_layouts,
10544                        &crease_trailers,
10545                        &row_block_types,
10546                        content_origin,
10547                        scroll_position,
10548                        scroll_pixel_position,
10549                        edit_prediction_popover_origin,
10550                        start_row,
10551                        end_row,
10552                        line_height,
10553                        em_width,
10554                        style,
10555                        window,
10556                        cx,
10557                    );
10558
10559                    let mut inline_blame_layout = None;
10560                    let mut inline_code_actions = None;
10561                    if let Some(newest_selection_head) = newest_selection_head {
10562                        let display_row = newest_selection_head.row();
10563                        if (start_row..end_row).contains(&display_row)
10564                            && !row_block_types.contains_key(&display_row)
10565                        {
10566                            inline_code_actions = self.layout_inline_code_actions(
10567                                newest_selection_head,
10568                                content_origin,
10569                                scroll_position,
10570                                scroll_pixel_position,
10571                                line_height,
10572                                &snapshot,
10573                                window,
10574                                cx,
10575                            );
10576
10577                            let line_ix = display_row.minus(start_row) as usize;
10578                            if let (Some(row_info), Some(line_layout), Some(crease_trailer)) = (
10579                                row_infos.get(line_ix),
10580                                line_layouts.get(line_ix),
10581                                crease_trailers.get(line_ix),
10582                            ) {
10583                                let crease_trailer_layout = crease_trailer.as_ref();
10584                                if let Some(layout) = self.layout_inline_blame(
10585                                    display_row,
10586                                    row_info,
10587                                    line_layout,
10588                                    crease_trailer_layout,
10589                                    em_width,
10590                                    content_origin,
10591                                    scroll_position,
10592                                    scroll_pixel_position,
10593                                    line_height,
10594                                    window,
10595                                    cx,
10596                                ) {
10597                                    inline_blame_layout = Some(layout);
10598                                    // Blame overrides inline diagnostics
10599                                    inline_diagnostics.remove(&display_row);
10600                                }
10601                            } else {
10602                                log::error!(
10603                                    "bug: line_ix {} is out of bounds - row_infos.len(): {}, \
10604                                    line_layouts.len(): {}, \
10605                                    crease_trailers.len(): {}",
10606                                    line_ix,
10607                                    row_infos.len(),
10608                                    line_layouts.len(),
10609                                    crease_trailers.len(),
10610                                );
10611                            }
10612                        }
10613                    }
10614
10615                    let blamed_display_rows = self.layout_blame_entries(
10616                        &row_infos,
10617                        em_width,
10618                        scroll_position,
10619                        line_height,
10620                        &gutter_hitbox,
10621                        gutter_dimensions.git_blame_entries_width,
10622                        window,
10623                        cx,
10624                    );
10625
10626                    let line_elements = self.prepaint_lines(
10627                        start_row,
10628                        &mut line_layouts,
10629                        line_height,
10630                        scroll_position,
10631                        scroll_pixel_position,
10632                        content_origin,
10633                        window,
10634                        cx,
10635                    );
10636
10637                    window.with_element_namespace("blocks", |window| {
10638                        self.layout_blocks(
10639                            &mut blocks,
10640                            &hitbox,
10641                            &gutter_hitbox,
10642                            line_height,
10643                            scroll_position,
10644                            scroll_pixel_position,
10645                            &editor_margins,
10646                            window,
10647                            cx,
10648                        );
10649                        self.layout_blocks(
10650                            &mut spacer_blocks,
10651                            &hitbox,
10652                            &gutter_hitbox,
10653                            line_height,
10654                            scroll_position,
10655                            scroll_pixel_position,
10656                            &editor_margins,
10657                            window,
10658                            cx,
10659                        );
10660                    });
10661
10662                    let cursors = self.collect_cursors(&snapshot, cx);
10663                    let visible_row_range = start_row..end_row;
10664                    let non_visible_cursors = cursors
10665                        .iter()
10666                        .any(|c| !visible_row_range.contains(&c.0.row()));
10667
10668                    let visible_cursors = self.layout_visible_cursors(
10669                        &snapshot,
10670                        &selections,
10671                        &row_block_types,
10672                        start_row..end_row,
10673                        &line_layouts,
10674                        &text_hitbox,
10675                        content_origin,
10676                        scroll_position,
10677                        scroll_pixel_position,
10678                        line_height,
10679                        em_width,
10680                        em_advance,
10681                        autoscroll_containing_element,
10682                        &redacted_ranges,
10683                        window,
10684                        cx,
10685                    );
10686
10687                    let scrollbars_layout = self.layout_scrollbars(
10688                        &snapshot,
10689                        &scrollbar_layout_information,
10690                        content_offset,
10691                        scroll_position,
10692                        non_visible_cursors,
10693                        right_margin,
10694                        editor_width,
10695                        window,
10696                        cx,
10697                    );
10698
10699                    let gutter_settings = EditorSettings::get_global(cx).gutter;
10700
10701                    let context_menu_layout =
10702                        if let Some(newest_selection_head) = newest_selection_head {
10703                            let newest_selection_point =
10704                                newest_selection_head.to_point(&snapshot.display_snapshot);
10705                            if (start_row..end_row).contains(&newest_selection_head.row()) {
10706                                self.layout_cursor_popovers(
10707                                    line_height,
10708                                    &text_hitbox,
10709                                    content_origin,
10710                                    right_margin,
10711                                    start_row,
10712                                    scroll_pixel_position,
10713                                    &line_layouts,
10714                                    newest_selection_head,
10715                                    newest_selection_point,
10716                                    style,
10717                                    window,
10718                                    cx,
10719                                )
10720                            } else {
10721                                None
10722                            }
10723                        } else {
10724                            None
10725                        };
10726
10727                    self.layout_gutter_menu(
10728                        line_height,
10729                        &text_hitbox,
10730                        content_origin,
10731                        right_margin,
10732                        scroll_pixel_position,
10733                        gutter_dimensions.width - gutter_dimensions.left_padding,
10734                        window,
10735                        cx,
10736                    );
10737
10738                    let test_indicators = if gutter_settings.runnables {
10739                        self.layout_run_indicators(
10740                            &gutter,
10741                            &run_indicator_rows,
10742                            &breakpoint_rows,
10743                            window,
10744                            cx,
10745                        )
10746                    } else {
10747                        Vec::new()
10748                    };
10749
10750                    let show_bookmarks =
10751                        snapshot.show_bookmarks.unwrap_or(gutter_settings.bookmarks);
10752
10753                    let bookmark_rows = self.editor.update(cx, |editor, cx| {
10754                        let mut rows = editor.active_bookmarks(start_row..end_row, window, cx);
10755                        rows.retain(|k| !run_indicator_rows.contains(k));
10756                        rows.retain(|k| !breakpoint_rows.contains_key(k));
10757                        rows
10758                    });
10759
10760                    let bookmarks = if show_bookmarks {
10761                        self.layout_bookmarks(&gutter, &bookmark_rows, window, cx)
10762                    } else {
10763                        Vec::new()
10764                    };
10765
10766                    let show_breakpoints = snapshot
10767                        .show_breakpoints
10768                        .unwrap_or(gutter_settings.breakpoints);
10769
10770                    breakpoint_rows.retain(|k, _| !run_indicator_rows.contains(k));
10771                    let mut breakpoints = if show_breakpoints {
10772                        self.layout_breakpoints(&gutter, &breakpoint_rows, window, cx)
10773                    } else {
10774                        Vec::new()
10775                    };
10776
10777                    let gutter_hover_button = self
10778                        .editor
10779                        .read(cx)
10780                        .gutter_hover_button
10781                        .0
10782                        .filter(|phantom| phantom.is_active)
10783                        .map(|phantom| phantom.display_row);
10784
10785                    if let Some(row) = gutter_hover_button
10786                        && !breakpoint_rows.contains_key(&row)
10787                        && !run_indicator_rows.contains(&row)
10788                        && !bookmark_rows.contains(&row)
10789                        && (show_bookmarks || show_breakpoints)
10790                    {
10791                        let position = snapshot
10792                            .display_point_to_anchor(DisplayPoint::new(row, 0), Bias::Right);
10793                        breakpoints.extend(
10794                            self.layout_gutter_hover_button(&gutter, position, row, window, cx),
10795                        );
10796                    }
10797
10798                    let git_gutter_width = Self::gutter_strip_width(line_height)
10799                        + gutter_dimensions
10800                            .git_blame_entries_width
10801                            .unwrap_or_default();
10802                    let available_width = gutter_dimensions.left_padding - git_gutter_width;
10803
10804                    let max_line_number_length = self
10805                        .editor
10806                        .read(cx)
10807                        .buffer()
10808                        .read(cx)
10809                        .snapshot(cx)
10810                        .widest_line_number()
10811                        .ilog10()
10812                        + 1;
10813
10814                    let diff_review_button = self
10815                        .should_render_diff_review_button(
10816                            start_row..end_row,
10817                            &row_infos,
10818                            &snapshot,
10819                            cx,
10820                        )
10821                        .map(|(display_row, buffer_row)| {
10822                            let is_wide = max_line_number_length
10823                                >= EditorSettings::get_global(cx).gutter.min_line_number_digits
10824                                    as u32
10825                                && buffer_row.is_some_and(|row| {
10826                                    (row + 1).ilog10() + 1 == max_line_number_length
10827                                })
10828                                || gutter_dimensions.right_padding == px(0.);
10829
10830                            let button_width = if is_wide {
10831                                available_width - px(6.)
10832                            } else {
10833                                available_width + em_width - px(6.)
10834                            };
10835
10836                            let button = self.editor.update(cx, |editor, cx| {
10837                                editor
10838                                    .render_diff_review_button(display_row, button_width, cx)
10839                                    .into_any_element()
10840                            });
10841                            gutter.prepaint_button(button, display_row, window, cx)
10842                        });
10843
10844                    self.layout_signature_help(
10845                        &hitbox,
10846                        content_origin,
10847                        scroll_pixel_position,
10848                        newest_selection_head,
10849                        start_row,
10850                        &line_layouts,
10851                        line_height,
10852                        em_width,
10853                        context_menu_layout,
10854                        window,
10855                        cx,
10856                    );
10857
10858                    if !cx.has_active_drag() {
10859                        self.layout_hover_popovers(
10860                            &snapshot,
10861                            &hitbox,
10862                            start_row..end_row,
10863                            content_origin,
10864                            scroll_pixel_position,
10865                            &line_layouts,
10866                            line_height,
10867                            em_width,
10868                            context_menu_layout,
10869                            window,
10870                            cx,
10871                        );
10872
10873                        self.layout_blame_popover(&snapshot, &hitbox, line_height, window, cx);
10874                    }
10875
10876                    let mouse_context_menu = self.layout_mouse_context_menu(
10877                        &snapshot,
10878                        start_row..end_row,
10879                        content_origin,
10880                        window,
10881                        cx,
10882                    );
10883
10884                    window.with_element_namespace("crease_toggles", |window| {
10885                        self.prepaint_crease_toggles(
10886                            &mut crease_toggles,
10887                            line_height,
10888                            &gutter_dimensions,
10889                            gutter_settings,
10890                            scroll_pixel_position,
10891                            &gutter_hitbox,
10892                            window,
10893                            cx,
10894                        )
10895                    });
10896
10897                    window.with_element_namespace("expand_toggles", |window| {
10898                        self.prepaint_expand_toggles(&mut expand_toggles, window, cx)
10899                    });
10900
10901                    let wrap_guides = self.layout_wrap_guides(
10902                        em_advance,
10903                        scroll_position,
10904                        content_origin,
10905                        scrollbars_layout.as_ref(),
10906                        vertical_scrollbar_width,
10907                        &hitbox,
10908                        window,
10909                        cx,
10910                    );
10911
10912                    let minimap = window.with_element_namespace("minimap", |window| {
10913                        self.layout_minimap(
10914                            &snapshot,
10915                            minimap_width,
10916                            scroll_position,
10917                            &scrollbar_layout_information,
10918                            scrollbars_layout.as_ref(),
10919                            window,
10920                            cx,
10921                        )
10922                    });
10923
10924                    let invisible_symbol_font_size = font_size / 2.;
10925                    let whitespace_map = &self
10926                        .editor
10927                        .read(cx)
10928                        .buffer
10929                        .read(cx)
10930                        .language_settings(cx)
10931                        .whitespace_map;
10932
10933                    let tab_char = whitespace_map.tab.clone();
10934                    let tab_len = tab_char.len();
10935                    let tab_invisible = window.text_system().shape_line(
10936                        tab_char,
10937                        invisible_symbol_font_size,
10938                        &[TextRun {
10939                            len: tab_len,
10940                            font: self.style.text.font(),
10941                            color: cx.theme().colors().editor_invisible,
10942                            ..Default::default()
10943                        }],
10944                        None,
10945                    );
10946
10947                    let space_char = whitespace_map.space.clone();
10948                    let space_len = space_char.len();
10949                    let space_invisible = window.text_system().shape_line(
10950                        space_char,
10951                        invisible_symbol_font_size,
10952                        &[TextRun {
10953                            len: space_len,
10954                            font: self.style.text.font(),
10955                            color: cx.theme().colors().editor_invisible,
10956                            ..Default::default()
10957                        }],
10958                        None,
10959                    );
10960
10961                    let mode = snapshot.mode.clone();
10962
10963                    let sticky_scroll_header_height = sticky_headers
10964                        .as_ref()
10965                        .and_then(|headers| headers.lines.last())
10966                        .map_or(Pixels::ZERO, |last| last.offset + line_height);
10967
10968                    let has_sticky_buffer_header =
10969                        sticky_buffer_header.is_some() || sticky_header_excerpt_id.is_some();
10970                    let sticky_header_height = if has_sticky_buffer_header {
10971                        let full_height = FILE_HEADER_HEIGHT as f32 * line_height;
10972                        let display_row = blocks
10973                            .iter()
10974                            .filter(|block| block.is_buffer_header)
10975                            .find_map(|block| {
10976                                block.row.filter(|row| row.0 > scroll_position.y as u32)
10977                            });
10978                        let offset = match display_row {
10979                            Some(display_row) => {
10980                                let max_row = display_row.0.saturating_sub(FILE_HEADER_HEIGHT);
10981                                let offset = (scroll_position.y - max_row as f64).max(0.0);
10982                                let slide_up =
10983                                    Pixels::from(offset * ScrollPixelOffset::from(line_height));
10984
10985                                (full_height - slide_up).max(Pixels::ZERO)
10986                            }
10987                            None => full_height,
10988                        };
10989                        let header_bottom_padding =
10990                            BUFFER_HEADER_PADDING.to_pixels(window.rem_size());
10991                        sticky_scroll_header_height + offset - header_bottom_padding
10992                    } else {
10993                        sticky_scroll_header_height
10994                    };
10995
10996                    let (diff_hunk_controls, diff_hunk_control_bounds) =
10997                        if is_read_only && !self.editor.read(cx).delegate_stage_and_restore {
10998                            (vec![], vec![])
10999                        } else {
11000                            self.layout_diff_hunk_controls(
11001                                start_row..end_row,
11002                                &row_infos,
11003                                &text_hitbox,
11004                                current_selection_head,
11005                                line_height,
11006                                right_margin,
11007                                scroll_pixel_position,
11008                                sticky_header_height,
11009                                &display_hunks,
11010                                &highlighted_rows,
11011                                self.editor.clone(),
11012                                window,
11013                                cx,
11014                            )
11015                        };
11016
11017                    let position_map = Rc::new(PositionMap {
11018                        size: bounds.size,
11019                        visible_row_range,
11020                        scroll_position,
11021                        scroll_pixel_position,
11022                        scroll_max,
11023                        line_layouts,
11024                        line_height,
11025                        em_width,
11026                        em_advance,
11027                        em_layout_width,
11028                        snapshot,
11029                        text_align: self.style.text.text_align,
11030                        content_width: text_hitbox.size.width,
11031                        gutter_hitbox: gutter_hitbox.clone(),
11032                        text_hitbox: text_hitbox.clone(),
11033                        inline_blame_bounds: inline_blame_layout
11034                            .as_ref()
11035                            .map(|layout| (layout.bounds, layout.buffer_id, layout.entry.clone())),
11036                        display_hunks: display_hunks.clone(),
11037                        diff_hunk_control_bounds,
11038                    });
11039
11040                    self.editor.update(cx, |editor, _| {
11041                        editor.last_position_map = Some(position_map.clone())
11042                    });
11043
11044                    EditorLayout {
11045                        mode,
11046                        position_map,
11047                        visible_display_row_range: start_row..end_row,
11048                        wrap_guides,
11049                        indent_guides,
11050                        hitbox,
11051                        gutter_hitbox,
11052                        display_hunks,
11053                        content_origin,
11054                        scrollbars_layout,
11055                        minimap,
11056                        active_rows,
11057                        highlighted_rows,
11058                        highlighted_ranges,
11059                        highlighted_gutter_ranges,
11060                        redacted_ranges,
11061                        document_colors,
11062                        line_elements,
11063                        line_numbers,
11064                        blamed_display_rows,
11065                        inline_diagnostics,
11066                        inline_blame_layout,
11067                        inline_code_actions,
11068                        blocks,
11069                        spacer_blocks,
11070                        cursors,
11071                        visible_cursors,
11072                        selections,
11073                        edit_prediction_popover,
11074                        diff_hunk_controls,
11075                        mouse_context_menu,
11076                        test_indicators,
11077                        bookmarks,
11078                        breakpoints,
11079                        diff_review_button,
11080                        crease_toggles,
11081                        crease_trailers,
11082                        tab_invisible,
11083                        space_invisible,
11084                        sticky_buffer_header,
11085                        sticky_headers,
11086                        expand_toggles,
11087                        text_align: self.style.text.text_align,
11088                        content_width: text_hitbox.size.width,
11089                    }
11090                })
11091            })
11092        })
11093    }
11094
11095    fn paint(
11096        &mut self,
11097        _: Option<&GlobalElementId>,
11098        _inspector_id: Option<&gpui::InspectorElementId>,
11099        bounds: Bounds<gpui::Pixels>,
11100        _: &mut Self::RequestLayoutState,
11101        layout: &mut Self::PrepaintState,
11102        window: &mut Window,
11103        cx: &mut App,
11104    ) {
11105        if !layout.mode.is_minimap() {
11106            let focus_handle = self.editor.focus_handle(cx);
11107            let key_context = self
11108                .editor
11109                .update(cx, |editor, cx| editor.key_context(window, cx));
11110
11111            window.set_key_context(key_context);
11112            window.handle_input(
11113                &focus_handle,
11114                ElementInputHandler::new(bounds, self.editor.clone()),
11115                cx,
11116            );
11117            self.register_actions(window, cx);
11118            self.register_key_listeners(window, cx, layout);
11119        }
11120
11121        let text_style = TextStyleRefinement {
11122            font_size: Some(self.style.text.font_size),
11123            line_height: Some(self.style.text.line_height),
11124            ..Default::default()
11125        };
11126        let rem_size = self.rem_size(cx);
11127        window.with_rem_size(rem_size, |window| {
11128            window.with_text_style(Some(text_style), |window| {
11129                window.with_content_mask(Some(ContentMask { bounds }), |window| {
11130                    self.paint_mouse_listeners(layout, window, cx);
11131
11132                    // Mask the editor behind sticky scroll headers. Important
11133                    // for transparent backgrounds.
11134                    let below_sticky_headers_mask = layout
11135                        .sticky_headers
11136                        .as_ref()
11137                        .and_then(|h| h.lines.last())
11138                        .map(|last| ContentMask {
11139                            bounds: Bounds {
11140                                origin: point(
11141                                    bounds.origin.x,
11142                                    bounds.origin.y + last.offset + layout.position_map.line_height,
11143                                ),
11144                                size: size(
11145                                    bounds.size.width,
11146                                    (bounds.size.height
11147                                        - last.offset
11148                                        - layout.position_map.line_height)
11149                                        .max(Pixels::ZERO),
11150                                ),
11151                            },
11152                        });
11153
11154                    window.with_content_mask(below_sticky_headers_mask, |window| {
11155                        self.paint_background(layout, window, cx);
11156
11157                        self.paint_indent_guides(layout, window, cx);
11158
11159                        if layout.gutter_hitbox.size.width > Pixels::ZERO {
11160                            self.paint_blamed_display_rows(layout, window, cx);
11161                            self.paint_line_numbers(layout, window, cx);
11162                        }
11163
11164                        self.paint_text(layout, window, cx);
11165
11166                        if !layout.spacer_blocks.is_empty() {
11167                            window.with_element_namespace("blocks", |window| {
11168                                self.paint_spacer_blocks(layout, window, cx);
11169                            });
11170                        }
11171
11172                        if layout.gutter_hitbox.size.width > Pixels::ZERO {
11173                            self.paint_gutter_highlights(layout, window, cx);
11174                            self.paint_gutter_indicators(layout, window, cx);
11175                        }
11176
11177                        if !layout.blocks.is_empty() {
11178                            window.with_element_namespace("blocks", |window| {
11179                                self.paint_non_spacer_blocks(layout, window, cx);
11180                            });
11181                        }
11182                    });
11183
11184                    window.with_element_namespace("blocks", |window| {
11185                        if let Some(mut sticky_header) = layout.sticky_buffer_header.take() {
11186                            sticky_header.paint(window, cx)
11187                        }
11188                    });
11189
11190                    self.paint_sticky_headers(layout, window, cx);
11191                    self.paint_minimap(layout, window, cx);
11192                    self.paint_scrollbars(layout, window, cx);
11193                    self.paint_edit_prediction_popover(layout, window, cx);
11194                    self.paint_mouse_context_menu(layout, window, cx);
11195                });
11196            })
11197        })
11198    }
11199}
11200
11201pub(super) fn gutter_bounds(
11202    editor_bounds: Bounds<Pixels>,
11203    gutter_dimensions: GutterDimensions,
11204) -> Bounds<Pixels> {
11205    Bounds {
11206        origin: editor_bounds.origin,
11207        size: size(gutter_dimensions.width, editor_bounds.size.height),
11208    }
11209}
11210
11211#[derive(Clone, Copy)]
11212struct ContextMenuLayout {
11213    y_flipped: bool,
11214    bounds: Bounds<Pixels>,
11215}
11216
11217/// Holds information required for layouting the editor scrollbars.
11218struct ScrollbarLayoutInformation {
11219    /// The bounds of the editor area (excluding the content offset).
11220    editor_bounds: Bounds<Pixels>,
11221    /// The available range to scroll within the document.
11222    scroll_range: Size<Pixels>,
11223    /// The space available for one glyph in the editor.
11224    glyph_grid_cell: Size<Pixels>,
11225}
11226
11227impl ScrollbarLayoutInformation {
11228    pub fn new(
11229        editor_bounds: Bounds<Pixels>,
11230        glyph_grid_cell: Size<Pixels>,
11231        document_size: Size<Pixels>,
11232        longest_line_blame_width: Pixels,
11233        settings: &EditorSettings,
11234        scroll_beyond_last_line: ScrollBeyondLastLine,
11235    ) -> Self {
11236        let vertical_overscroll = match scroll_beyond_last_line {
11237            ScrollBeyondLastLine::OnePage => editor_bounds.size.height,
11238            ScrollBeyondLastLine::Off => glyph_grid_cell.height,
11239            ScrollBeyondLastLine::VerticalScrollMargin => {
11240                (1.0 + settings.vertical_scroll_margin) as f32 * glyph_grid_cell.height
11241            }
11242        };
11243
11244        let overscroll = size(longest_line_blame_width, vertical_overscroll);
11245
11246        ScrollbarLayoutInformation {
11247            editor_bounds,
11248            scroll_range: document_size + overscroll,
11249            glyph_grid_cell,
11250        }
11251    }
11252}
11253
11254impl IntoElement for EditorElement {
11255    type Element = Self;
11256
11257    fn into_element(self) -> Self::Element {
11258        self
11259    }
11260}
11261
11262pub struct EditorLayout {
11263    position_map: Rc<PositionMap>,
11264    hitbox: Hitbox,
11265    gutter_hitbox: Hitbox,
11266    content_origin: gpui::Point<Pixels>,
11267    scrollbars_layout: Option<EditorScrollbars>,
11268    minimap: Option<MinimapLayout>,
11269    mode: EditorMode,
11270    wrap_guides: SmallVec<[(Pixels, bool); 2]>,
11271    indent_guides: Option<Vec<IndentGuideLayout>>,
11272    visible_display_row_range: Range<DisplayRow>,
11273    active_rows: BTreeMap<DisplayRow, LineHighlightSpec>,
11274    highlighted_rows: BTreeMap<DisplayRow, LineHighlight>,
11275    line_elements: SmallVec<[AnyElement; 1]>,
11276    line_numbers: Arc<HashMap<MultiBufferRow, LineNumberLayout>>,
11277    display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
11278    blamed_display_rows: Option<Vec<AnyElement>>,
11279    inline_diagnostics: HashMap<DisplayRow, AnyElement>,
11280    inline_blame_layout: Option<InlineBlameLayout>,
11281    inline_code_actions: Option<AnyElement>,
11282    blocks: Vec<BlockLayout>,
11283    spacer_blocks: Vec<BlockLayout>,
11284    highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
11285    highlighted_gutter_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
11286    redacted_ranges: Vec<Range<DisplayPoint>>,
11287    cursors: Vec<(DisplayPoint, Hsla)>,
11288    visible_cursors: Vec<CursorLayout>,
11289    selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
11290    test_indicators: Vec<AnyElement>,
11291    bookmarks: Vec<AnyElement>,
11292    breakpoints: Vec<AnyElement>,
11293    diff_review_button: Option<AnyElement>,
11294    crease_toggles: Vec<Option<AnyElement>>,
11295    expand_toggles: Vec<Option<(AnyElement, gpui::Point<Pixels>)>>,
11296    diff_hunk_controls: Vec<AnyElement>,
11297    crease_trailers: Vec<Option<CreaseTrailerLayout>>,
11298    edit_prediction_popover: Option<AnyElement>,
11299    mouse_context_menu: Option<AnyElement>,
11300    tab_invisible: ShapedLine,
11301    space_invisible: ShapedLine,
11302    sticky_buffer_header: Option<AnyElement>,
11303    sticky_headers: Option<StickyHeaders>,
11304    document_colors: Option<(DocumentColorsRenderMode, Vec<(Range<DisplayPoint>, Hsla)>)>,
11305    text_align: TextAlign,
11306    content_width: Pixels,
11307}
11308
11309struct StickyHeaders {
11310    lines: Vec<StickyHeaderLine>,
11311    gutter_background: Hsla,
11312    content_background: Hsla,
11313    gutter_right_padding: Pixels,
11314}
11315
11316struct StickyHeaderLine {
11317    row: DisplayRow,
11318    offset: Pixels,
11319    line: Rc<LineWithInvisibles>,
11320    line_number: Option<ShapedLine>,
11321    elements: SmallVec<[AnyElement; 1]>,
11322    available_text_width: Pixels,
11323    hitbox: Hitbox,
11324}
11325
11326impl EditorLayout {
11327    fn line_end_overshoot(&self) -> Pixels {
11328        0.15 * self.position_map.line_height
11329    }
11330}
11331
11332impl StickyHeaders {
11333    fn paint(
11334        &mut self,
11335        layout: &mut EditorLayout,
11336        whitespace_setting: ShowWhitespaceSetting,
11337        window: &mut Window,
11338        cx: &mut App,
11339    ) {
11340        let line_height = layout.position_map.line_height;
11341
11342        for line in self.lines.iter_mut().rev() {
11343            window.paint_layer(
11344                Bounds::new(
11345                    layout.gutter_hitbox.origin + point(Pixels::ZERO, line.offset),
11346                    size(line.hitbox.size.width, line_height),
11347                ),
11348                |window| {
11349                    let gutter_bounds = Bounds::new(
11350                        layout.gutter_hitbox.origin + point(Pixels::ZERO, line.offset),
11351                        size(layout.gutter_hitbox.size.width, line_height),
11352                    );
11353                    window.paint_quad(fill(gutter_bounds, self.gutter_background));
11354
11355                    let text_bounds = Bounds::new(
11356                        layout.position_map.text_hitbox.origin + point(Pixels::ZERO, line.offset),
11357                        size(line.available_text_width, line_height),
11358                    );
11359                    window.paint_quad(fill(text_bounds, self.content_background));
11360
11361                    if line.hitbox.is_hovered(window) {
11362                        let hover_overlay = cx.theme().colors().panel_overlay_hover;
11363                        window.paint_quad(fill(gutter_bounds, hover_overlay));
11364                        window.paint_quad(fill(text_bounds, hover_overlay));
11365                    }
11366
11367                    line.paint(
11368                        layout,
11369                        self.gutter_right_padding,
11370                        line.available_text_width,
11371                        layout.content_origin,
11372                        line_height,
11373                        whitespace_setting,
11374                        window,
11375                        cx,
11376                    );
11377                },
11378            );
11379
11380            window.set_cursor_style(CursorStyle::IBeam, &line.hitbox);
11381        }
11382    }
11383}
11384
11385impl StickyHeaderLine {
11386    fn new(
11387        row: DisplayRow,
11388        offset: Pixels,
11389        mut line: LineWithInvisibles,
11390        line_number: Option<ShapedLine>,
11391        line_height: Pixels,
11392        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
11393        content_origin: gpui::Point<Pixels>,
11394        gutter_hitbox: &Hitbox,
11395        text_hitbox: &Hitbox,
11396        window: &mut Window,
11397        cx: &mut App,
11398    ) -> Self {
11399        let mut elements = SmallVec::<[AnyElement; 1]>::new();
11400        line.prepaint_with_custom_offset(
11401            line_height,
11402            scroll_pixel_position,
11403            content_origin,
11404            offset,
11405            &mut elements,
11406            window,
11407            cx,
11408        );
11409
11410        let hitbox_bounds = Bounds::new(
11411            gutter_hitbox.origin + point(Pixels::ZERO, offset),
11412            size(text_hitbox.right() - gutter_hitbox.left(), line_height),
11413        );
11414        let available_text_width =
11415            (hitbox_bounds.size.width - gutter_hitbox.size.width).max(Pixels::ZERO);
11416
11417        Self {
11418            row,
11419            offset,
11420            line: Rc::new(line),
11421            line_number,
11422            elements,
11423            available_text_width,
11424            hitbox: window.insert_hitbox(hitbox_bounds, HitboxBehavior::BlockMouseExceptScroll),
11425        }
11426    }
11427
11428    fn paint(
11429        &mut self,
11430        layout: &EditorLayout,
11431        gutter_right_padding: Pixels,
11432        available_text_width: Pixels,
11433        content_origin: gpui::Point<Pixels>,
11434        line_height: Pixels,
11435        whitespace_setting: ShowWhitespaceSetting,
11436        window: &mut Window,
11437        cx: &mut App,
11438    ) {
11439        window.with_content_mask(
11440            Some(ContentMask {
11441                bounds: Bounds::new(
11442                    layout.position_map.text_hitbox.bounds.origin
11443                        + point(Pixels::ZERO, self.offset),
11444                    size(available_text_width, line_height),
11445                ),
11446            }),
11447            |window| {
11448                self.line.draw_with_custom_offset(
11449                    layout,
11450                    self.row,
11451                    content_origin,
11452                    self.offset,
11453                    whitespace_setting,
11454                    &[],
11455                    window,
11456                    cx,
11457                );
11458                for element in &mut self.elements {
11459                    element.paint(window, cx);
11460                }
11461            },
11462        );
11463
11464        if let Some(line_number) = &self.line_number {
11465            let gutter_origin = layout.gutter_hitbox.origin + point(Pixels::ZERO, self.offset);
11466            let gutter_width = layout.gutter_hitbox.size.width;
11467            let origin = point(
11468                gutter_origin.x + gutter_width - gutter_right_padding - line_number.width,
11469                gutter_origin.y,
11470            );
11471            line_number
11472                .paint(origin, line_height, TextAlign::Left, None, window, cx)
11473                .log_err();
11474        }
11475    }
11476}
11477
11478#[derive(Debug)]
11479struct LineNumberSegment {
11480    shaped_line: ShapedLine,
11481    hitbox: Option<Hitbox>,
11482}
11483
11484#[derive(Debug)]
11485struct LineNumberLayout {
11486    segments: SmallVec<[LineNumberSegment; 1]>,
11487}
11488
11489struct ColoredRange<T> {
11490    start: T,
11491    end: T,
11492    color: Hsla,
11493}
11494
11495impl Along for ScrollbarAxes {
11496    type Unit = bool;
11497
11498    fn along(&self, axis: ScrollbarAxis) -> Self::Unit {
11499        match axis {
11500            ScrollbarAxis::Horizontal => self.horizontal,
11501            ScrollbarAxis::Vertical => self.vertical,
11502        }
11503    }
11504
11505    fn apply_along(&self, axis: ScrollbarAxis, f: impl FnOnce(Self::Unit) -> Self::Unit) -> Self {
11506        match axis {
11507            ScrollbarAxis::Horizontal => ScrollbarAxes {
11508                horizontal: f(self.horizontal),
11509                vertical: self.vertical,
11510            },
11511            ScrollbarAxis::Vertical => ScrollbarAxes {
11512                horizontal: self.horizontal,
11513                vertical: f(self.vertical),
11514            },
11515        }
11516    }
11517}
11518
11519#[derive(Clone)]
11520struct EditorScrollbars {
11521    pub vertical: Option<ScrollbarLayout>,
11522    pub horizontal: Option<ScrollbarLayout>,
11523    pub visible: bool,
11524}
11525
11526impl EditorScrollbars {
11527    pub fn from_scrollbar_axes(
11528        show_scrollbar: ScrollbarAxes,
11529        layout_information: &ScrollbarLayoutInformation,
11530        content_offset: gpui::Point<Pixels>,
11531        scroll_position: gpui::Point<f64>,
11532        scrollbar_width: Pixels,
11533        right_margin: Pixels,
11534        editor_width: Pixels,
11535        show_scrollbars: bool,
11536        scrollbar_state: Option<&ActiveScrollbarState>,
11537        window: &mut Window,
11538    ) -> Self {
11539        let ScrollbarLayoutInformation {
11540            editor_bounds,
11541            scroll_range,
11542            glyph_grid_cell,
11543        } = layout_information;
11544
11545        let viewport_size = size(editor_width, editor_bounds.size.height);
11546
11547        let scrollbar_bounds_for = |axis: ScrollbarAxis| match axis {
11548            ScrollbarAxis::Horizontal => Bounds::from_anchor_and_size(
11549                gpui::Anchor::BottomLeft,
11550                editor_bounds.bottom_left(),
11551                size(
11552                    // The horizontal viewport size differs from the space available for the
11553                    // horizontal scrollbar, so we have to manually stitch it together here.
11554                    editor_bounds.size.width - right_margin,
11555                    scrollbar_width,
11556                ),
11557            ),
11558            ScrollbarAxis::Vertical => Bounds::from_anchor_and_size(
11559                gpui::Anchor::TopRight,
11560                editor_bounds.top_right(),
11561                size(scrollbar_width, viewport_size.height),
11562            ),
11563        };
11564
11565        let mut create_scrollbar_layout = |axis| {
11566            let viewport_size = viewport_size.along(axis);
11567            let scroll_range = scroll_range.along(axis);
11568
11569            // We always want a vertical scrollbar track for scrollbar diagnostic visibility.
11570            (show_scrollbar.along(axis)
11571                && (axis == ScrollbarAxis::Vertical || scroll_range > viewport_size))
11572                .then(|| {
11573                    ScrollbarLayout::new(
11574                        window.insert_hitbox(scrollbar_bounds_for(axis), HitboxBehavior::Normal),
11575                        viewport_size,
11576                        scroll_range,
11577                        glyph_grid_cell.along(axis),
11578                        content_offset.along(axis),
11579                        scroll_position.along(axis),
11580                        show_scrollbars,
11581                        axis,
11582                    )
11583                    .with_thumb_state(
11584                        scrollbar_state.and_then(|state| state.thumb_state_for_axis(axis)),
11585                    )
11586                })
11587        };
11588
11589        Self {
11590            vertical: create_scrollbar_layout(ScrollbarAxis::Vertical),
11591            horizontal: create_scrollbar_layout(ScrollbarAxis::Horizontal),
11592            visible: show_scrollbars,
11593        }
11594    }
11595
11596    pub fn iter_scrollbars(&self) -> impl Iterator<Item = (&ScrollbarLayout, ScrollbarAxis)> + '_ {
11597        [
11598            (&self.vertical, ScrollbarAxis::Vertical),
11599            (&self.horizontal, ScrollbarAxis::Horizontal),
11600        ]
11601        .into_iter()
11602        .filter_map(|(scrollbar, axis)| scrollbar.as_ref().map(|s| (s, axis)))
11603    }
11604
11605    /// Returns the currently hovered scrollbar axis, if any.
11606    pub fn get_hovered_axis(&self, window: &Window) -> Option<(&ScrollbarLayout, ScrollbarAxis)> {
11607        self.iter_scrollbars()
11608            .find(|s| s.0.hitbox.is_hovered(window))
11609    }
11610}
11611
11612#[derive(Clone)]
11613struct ScrollbarLayout {
11614    hitbox: Hitbox,
11615    visible_range: Range<ScrollOffset>,
11616    text_unit_size: Pixels,
11617    thumb_bounds: Option<Bounds<Pixels>>,
11618    thumb_state: ScrollbarThumbState,
11619}
11620
11621impl ScrollbarLayout {
11622    const BORDER_WIDTH: Pixels = px(1.0);
11623    const LINE_MARKER_HEIGHT: Pixels = px(2.0);
11624    const MIN_MARKER_HEIGHT: Pixels = px(5.0);
11625    const MIN_THUMB_SIZE: Pixels = px(25.0);
11626
11627    fn new(
11628        scrollbar_track_hitbox: Hitbox,
11629        viewport_size: Pixels,
11630        scroll_range: Pixels,
11631        glyph_space: Pixels,
11632        content_offset: Pixels,
11633        scroll_position: ScrollOffset,
11634        show_thumb: bool,
11635        axis: ScrollbarAxis,
11636    ) -> Self {
11637        let track_bounds = scrollbar_track_hitbox.bounds;
11638        // The length of the track available to the scrollbar thumb. We deliberately
11639        // exclude the content size here so that the thumb aligns with the content.
11640        let track_length = track_bounds.size.along(axis) - content_offset;
11641
11642        Self::new_with_hitbox_and_track_length(
11643            scrollbar_track_hitbox,
11644            track_length,
11645            viewport_size,
11646            scroll_range.into(),
11647            glyph_space,
11648            content_offset.into(),
11649            scroll_position,
11650            show_thumb,
11651            axis,
11652        )
11653    }
11654
11655    fn for_minimap(
11656        minimap_track_hitbox: Hitbox,
11657        visible_lines: f64,
11658        total_editor_lines: f64,
11659        minimap_line_height: Pixels,
11660        scroll_position: ScrollOffset,
11661        minimap_scroll_top: ScrollOffset,
11662        show_thumb: bool,
11663    ) -> Self {
11664        // The scrollbar thumb size is calculated as
11665        // (visible_content/total_content) Γ— scrollbar_track_length.
11666        //
11667        // For the minimap's thumb layout, we leverage this by setting the
11668        // scrollbar track length to the entire document size (using minimap line
11669        // height). This creates a thumb that exactly represents the editor
11670        // viewport scaled to minimap proportions.
11671        //
11672        // We adjust the thumb position relative to `minimap_scroll_top` to
11673        // accommodate for the deliberately oversized track.
11674        //
11675        // This approach ensures that the minimap thumb accurately reflects the
11676        // editor's current scroll position whilst nicely synchronizing the minimap
11677        // thumb and scrollbar thumb.
11678        let scroll_range = total_editor_lines * f64::from(minimap_line_height);
11679        let viewport_size = visible_lines * f64::from(minimap_line_height);
11680
11681        let track_top_offset = -minimap_scroll_top * f64::from(minimap_line_height);
11682
11683        Self::new_with_hitbox_and_track_length(
11684            minimap_track_hitbox,
11685            Pixels::from(scroll_range),
11686            Pixels::from(viewport_size),
11687            scroll_range,
11688            minimap_line_height,
11689            track_top_offset,
11690            scroll_position,
11691            show_thumb,
11692            ScrollbarAxis::Vertical,
11693        )
11694    }
11695
11696    fn new_with_hitbox_and_track_length(
11697        scrollbar_track_hitbox: Hitbox,
11698        track_length: Pixels,
11699        viewport_size: Pixels,
11700        scroll_range: f64,
11701        glyph_space: Pixels,
11702        content_offset: ScrollOffset,
11703        scroll_position: ScrollOffset,
11704        show_thumb: bool,
11705        axis: ScrollbarAxis,
11706    ) -> Self {
11707        let text_units_per_page = viewport_size.to_f64() / glyph_space.to_f64();
11708        let visible_range = scroll_position..scroll_position + text_units_per_page;
11709        let total_text_units = scroll_range / glyph_space.to_f64();
11710
11711        let thumb_percentage = text_units_per_page / total_text_units;
11712        let thumb_size = Pixels::from(ScrollOffset::from(track_length) * thumb_percentage)
11713            .max(ScrollbarLayout::MIN_THUMB_SIZE)
11714            .min(track_length);
11715
11716        let text_unit_divisor = (total_text_units - text_units_per_page).max(0.);
11717
11718        let content_larger_than_viewport = text_unit_divisor > 0.;
11719
11720        let text_unit_size = if content_larger_than_viewport {
11721            Pixels::from(ScrollOffset::from(track_length - thumb_size) / text_unit_divisor)
11722        } else {
11723            glyph_space
11724        };
11725
11726        let thumb_bounds = (show_thumb && content_larger_than_viewport).then(|| {
11727            Self::thumb_bounds(
11728                &scrollbar_track_hitbox,
11729                content_offset,
11730                visible_range.start,
11731                text_unit_size,
11732                thumb_size,
11733                axis,
11734            )
11735        });
11736
11737        ScrollbarLayout {
11738            hitbox: scrollbar_track_hitbox,
11739            visible_range,
11740            text_unit_size,
11741            thumb_bounds,
11742            thumb_state: Default::default(),
11743        }
11744    }
11745
11746    fn with_thumb_state(self, thumb_state: Option<ScrollbarThumbState>) -> Self {
11747        if let Some(thumb_state) = thumb_state {
11748            Self {
11749                thumb_state,
11750                ..self
11751            }
11752        } else {
11753            self
11754        }
11755    }
11756
11757    fn thumb_bounds(
11758        scrollbar_track: &Hitbox,
11759        content_offset: f64,
11760        visible_range_start: f64,
11761        text_unit_size: Pixels,
11762        thumb_size: Pixels,
11763        axis: ScrollbarAxis,
11764    ) -> Bounds<Pixels> {
11765        let thumb_origin = scrollbar_track.origin.apply_along(axis, |origin| {
11766            origin
11767                + Pixels::from(
11768                    content_offset + visible_range_start * ScrollOffset::from(text_unit_size),
11769                )
11770        });
11771        Bounds::new(
11772            thumb_origin,
11773            scrollbar_track.size.apply_along(axis, |_| thumb_size),
11774        )
11775    }
11776
11777    fn thumb_hovered(&self, position: &gpui::Point<Pixels>) -> bool {
11778        self.thumb_bounds
11779            .is_some_and(|bounds| bounds.contains(position))
11780    }
11781
11782    fn marker_quads_for_ranges(
11783        &self,
11784        row_ranges: impl IntoIterator<Item = ColoredRange<DisplayRow>>,
11785        column: Option<usize>,
11786    ) -> Vec<PaintQuad> {
11787        struct MinMax {
11788            min: Pixels,
11789            max: Pixels,
11790        }
11791        let (x_range, height_limit) = if let Some(column) = column {
11792            let column_width = ((self.hitbox.size.width - Self::BORDER_WIDTH) / 3.0).floor();
11793            let start = Self::BORDER_WIDTH + (column as f32 * column_width);
11794            let end = start + column_width;
11795            (
11796                Range { start, end },
11797                MinMax {
11798                    min: Self::MIN_MARKER_HEIGHT,
11799                    max: px(f32::MAX),
11800                },
11801            )
11802        } else {
11803            (
11804                Range {
11805                    start: Self::BORDER_WIDTH,
11806                    end: self.hitbox.size.width,
11807                },
11808                MinMax {
11809                    min: Self::LINE_MARKER_HEIGHT,
11810                    max: Self::LINE_MARKER_HEIGHT,
11811                },
11812            )
11813        };
11814
11815        let row_to_y = |row: DisplayRow| row.as_f64() as f32 * self.text_unit_size;
11816        let mut pixel_ranges = row_ranges
11817            .into_iter()
11818            .map(|range| {
11819                let start_y = row_to_y(range.start);
11820                let end_y = row_to_y(range.end)
11821                    + self
11822                        .text_unit_size
11823                        .max(height_limit.min)
11824                        .min(height_limit.max);
11825                ColoredRange {
11826                    start: start_y,
11827                    end: end_y,
11828                    color: range.color,
11829                }
11830            })
11831            .peekable();
11832
11833        let mut quads = Vec::new();
11834        while let Some(mut pixel_range) = pixel_ranges.next() {
11835            while let Some(next_pixel_range) = pixel_ranges.peek() {
11836                if pixel_range.end >= next_pixel_range.start - px(1.0)
11837                    && pixel_range.color == next_pixel_range.color
11838                {
11839                    pixel_range.end = next_pixel_range.end.max(pixel_range.end);
11840                    pixel_ranges.next();
11841                } else {
11842                    break;
11843                }
11844            }
11845
11846            let bounds = Bounds::from_corners(
11847                point(x_range.start, pixel_range.start),
11848                point(x_range.end, pixel_range.end),
11849            );
11850            quads.push(quad(
11851                bounds,
11852                Corners::default(),
11853                pixel_range.color,
11854                Edges::default(),
11855                Hsla::transparent_black(),
11856                BorderStyle::default(),
11857            ));
11858        }
11859
11860        quads
11861    }
11862}
11863
11864struct MinimapLayout {
11865    pub minimap: AnyElement,
11866    pub thumb_layout: ScrollbarLayout,
11867    pub minimap_scroll_top: ScrollOffset,
11868    pub minimap_line_height: Pixels,
11869    pub thumb_border_style: MinimapThumbBorder,
11870    pub max_scroll_top: ScrollOffset,
11871}
11872
11873impl MinimapLayout {
11874    /// The minimum width of the minimap in columns. If the minimap is smaller than this, it will be hidden.
11875    const MINIMAP_MIN_WIDTH_COLUMNS: f32 = 20.;
11876    /// The minimap width as a percentage of the editor width.
11877    const MINIMAP_WIDTH_PCT: f32 = 0.15;
11878    /// Calculates the scroll top offset the minimap editor has to have based on the
11879    /// current scroll progress.
11880    fn calculate_minimap_top_offset(
11881        document_lines: f64,
11882        visible_editor_lines: f64,
11883        visible_minimap_lines: f64,
11884        scroll_position: f64,
11885    ) -> ScrollOffset {
11886        let non_visible_document_lines = (document_lines - visible_editor_lines).max(0.);
11887        if non_visible_document_lines == 0. {
11888            0.
11889        } else {
11890            let scroll_percentage = (scroll_position / non_visible_document_lines).clamp(0., 1.);
11891            scroll_percentage * (document_lines - visible_minimap_lines).max(0.)
11892        }
11893    }
11894}
11895
11896struct CreaseTrailerLayout {
11897    element: AnyElement,
11898    bounds: Bounds<Pixels>,
11899}
11900
11901pub(crate) struct PositionMap {
11902    pub size: Size<Pixels>,
11903    pub line_height: Pixels,
11904    pub scroll_position: gpui::Point<ScrollOffset>,
11905    pub scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
11906    pub scroll_max: gpui::Point<ScrollOffset>,
11907    pub em_width: Pixels,
11908    pub em_advance: Pixels,
11909    pub em_layout_width: Pixels,
11910    pub visible_row_range: Range<DisplayRow>,
11911    pub line_layouts: Vec<LineWithInvisibles>,
11912    pub snapshot: EditorSnapshot,
11913    pub text_align: TextAlign,
11914    pub content_width: Pixels,
11915    pub text_hitbox: Hitbox,
11916    pub gutter_hitbox: Hitbox,
11917    pub inline_blame_bounds: Option<(Bounds<Pixels>, BufferId, BlameEntry)>,
11918    pub display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
11919    pub diff_hunk_control_bounds: Vec<(DisplayRow, Bounds<Pixels>)>,
11920}
11921
11922#[derive(Debug, Copy, Clone)]
11923pub struct PointForPosition {
11924    pub previous_valid: DisplayPoint,
11925    pub next_valid: DisplayPoint,
11926    pub nearest_valid: DisplayPoint,
11927    pub exact_unclipped: DisplayPoint,
11928    pub column_overshoot_after_line_end: u32,
11929}
11930
11931impl PointForPosition {
11932    pub fn as_valid(&self) -> Option<DisplayPoint> {
11933        if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped {
11934            Some(self.previous_valid)
11935        } else {
11936            None
11937        }
11938    }
11939
11940    pub fn intersects_selection(&self, selection: &Selection<DisplayPoint>) -> bool {
11941        let Some(valid_point) = self.as_valid() else {
11942            return false;
11943        };
11944        let range = selection.range();
11945
11946        let candidate_row = valid_point.row();
11947        let candidate_col = valid_point.column();
11948
11949        let start_row = range.start.row();
11950        let start_col = range.start.column();
11951        let end_row = range.end.row();
11952        let end_col = range.end.column();
11953
11954        if candidate_row < start_row || candidate_row > end_row {
11955            false
11956        } else if start_row == end_row {
11957            candidate_col >= start_col && candidate_col < end_col
11958        } else if candidate_row == start_row {
11959            candidate_col >= start_col
11960        } else if candidate_row == end_row {
11961            candidate_col < end_col
11962        } else {
11963            true
11964        }
11965    }
11966}
11967
11968impl PositionMap {
11969    pub(crate) fn point_for_position(&self, position: gpui::Point<Pixels>) -> PointForPosition {
11970        let text_bounds = self.text_hitbox.bounds;
11971        let scroll_position = self.snapshot.scroll_position();
11972        let position = position - text_bounds.origin;
11973        let y = position.y.max(px(0.)).min(self.size.height);
11974        let x = position.x + (scroll_position.x as f32 * self.em_layout_width);
11975        let row = ((y / self.line_height) as f64 + scroll_position.y) as u32;
11976
11977        let (column, x_overshoot_after_line_end) = if let Some(line) = self
11978            .line_layouts
11979            .get(row as usize - scroll_position.y as usize)
11980        {
11981            let alignment_offset = line.alignment_offset(self.text_align, self.content_width);
11982            let x_relative_to_text = x - alignment_offset;
11983            if let Some(ix) = line.index_for_x(x_relative_to_text) {
11984                (ix as u32, px(0.))
11985            } else {
11986                (line.len as u32, px(0.).max(x_relative_to_text - line.width))
11987            }
11988        } else {
11989            (0, x)
11990        };
11991
11992        let mut exact_unclipped = DisplayPoint::new(DisplayRow(row), column);
11993        let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
11994        let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
11995
11996        let nearest_valid = if previous_valid == next_valid {
11997            previous_valid
11998        } else {
11999            match self.snapshot.inlay_bias_at(exact_unclipped) {
12000                Some(Bias::Left) => next_valid,
12001                Some(Bias::Right) => previous_valid,
12002                None => previous_valid,
12003            }
12004        };
12005
12006        let column_overshoot_after_line_end =
12007            (x_overshoot_after_line_end / self.em_layout_width) as u32;
12008        *exact_unclipped.column_mut() += column_overshoot_after_line_end;
12009        PointForPosition {
12010            previous_valid,
12011            next_valid,
12012            nearest_valid,
12013            exact_unclipped,
12014            column_overshoot_after_line_end,
12015        }
12016    }
12017
12018    fn point_for_position_on_line(
12019        &self,
12020        position: gpui::Point<Pixels>,
12021        row: DisplayRow,
12022        line: &LineWithInvisibles,
12023    ) -> PointForPosition {
12024        let text_bounds = self.text_hitbox.bounds;
12025        let scroll_position = self.snapshot.scroll_position();
12026        let position = position - text_bounds.origin;
12027        let x = position.x + (scroll_position.x as f32 * self.em_layout_width);
12028
12029        let alignment_offset = line.alignment_offset(self.text_align, self.content_width);
12030        let x_relative_to_text = x - alignment_offset;
12031        let (column, x_overshoot_after_line_end) =
12032            if let Some(ix) = line.index_for_x(x_relative_to_text) {
12033                (ix as u32, px(0.))
12034            } else {
12035                (line.len as u32, px(0.).max(x_relative_to_text - line.width))
12036            };
12037
12038        let mut exact_unclipped = DisplayPoint::new(row, column);
12039        let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
12040        let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
12041
12042        let nearest_valid = if previous_valid == next_valid {
12043            previous_valid
12044        } else {
12045            match self.snapshot.inlay_bias_at(exact_unclipped) {
12046                Some(Bias::Left) => next_valid,
12047                Some(Bias::Right) => previous_valid,
12048                None => previous_valid,
12049            }
12050        };
12051
12052        let column_overshoot_after_line_end =
12053            (x_overshoot_after_line_end / self.em_layout_width) as u32;
12054        *exact_unclipped.column_mut() += column_overshoot_after_line_end;
12055        PointForPosition {
12056            previous_valid,
12057            next_valid,
12058            nearest_valid,
12059            exact_unclipped,
12060            column_overshoot_after_line_end,
12061        }
12062    }
12063}
12064
12065pub(crate) struct BlockLayout {
12066    pub(crate) id: BlockId,
12067    pub(crate) x_offset: Pixels,
12068    pub(crate) row: Option<DisplayRow>,
12069    pub(crate) element: AnyElement,
12070    pub(crate) available_space: Size<AvailableSpace>,
12071    pub(crate) style: BlockStyle,
12072    pub(crate) overlaps_gutter: bool,
12073    pub(crate) is_buffer_header: bool,
12074}
12075
12076pub fn layout_line(
12077    row: DisplayRow,
12078    snapshot: &EditorSnapshot,
12079    style: &EditorStyle,
12080    text_width: Pixels,
12081    is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
12082    window: &mut Window,
12083    cx: &mut App,
12084) -> LineWithInvisibles {
12085    let use_tree_sitter =
12086        !snapshot.semantic_tokens_enabled || snapshot.use_tree_sitter_for_syntax(row, cx);
12087    let language_aware = LanguageAwareStyling {
12088        tree_sitter: use_tree_sitter,
12089        diagnostics: true,
12090    };
12091    let chunks = snapshot.highlighted_chunks(row..row + DisplayRow(1), language_aware, style);
12092    LineWithInvisibles::from_chunks(
12093        chunks,
12094        style,
12095        MAX_LINE_LEN,
12096        1,
12097        &snapshot.mode,
12098        text_width,
12099        is_row_soft_wrapped,
12100        &[],
12101        window,
12102        cx,
12103    )
12104    .pop()
12105    .unwrap()
12106}
12107
12108#[derive(Debug, Clone)]
12109pub struct IndentGuideLayout {
12110    origin: gpui::Point<Pixels>,
12111    length: Pixels,
12112    single_indent_width: Pixels,
12113    display_row_range: Range<DisplayRow>,
12114    depth: u32,
12115    active: bool,
12116    settings: IndentGuideSettings,
12117}
12118
12119pub struct CursorLayout {
12120    origin: gpui::Point<Pixels>,
12121    block_width: Pixels,
12122    line_height: Pixels,
12123    color: Hsla,
12124    shape: CursorShape,
12125    block_text: Option<ShapedLine>,
12126    cursor_name: Option<AnyElement>,
12127}
12128
12129#[derive(Debug)]
12130pub struct CursorName {
12131    string: SharedString,
12132    color: Hsla,
12133    is_top_row: bool,
12134}
12135
12136impl CursorLayout {
12137    pub fn new(
12138        origin: gpui::Point<Pixels>,
12139        block_width: Pixels,
12140        line_height: Pixels,
12141        color: Hsla,
12142        shape: CursorShape,
12143        block_text: Option<ShapedLine>,
12144    ) -> CursorLayout {
12145        CursorLayout {
12146            origin,
12147            block_width,
12148            line_height,
12149            color,
12150            shape,
12151            block_text,
12152            cursor_name: None,
12153        }
12154    }
12155
12156    pub fn bounding_rect(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
12157        Bounds {
12158            origin: self.origin + origin,
12159            size: size(self.block_width, self.line_height),
12160        }
12161    }
12162
12163    fn bounds(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
12164        match self.shape {
12165            CursorShape::Bar => Bounds {
12166                origin: self.origin + origin,
12167                size: size(px(2.0), self.line_height),
12168            },
12169            CursorShape::Block | CursorShape::Hollow => Bounds {
12170                origin: self.origin + origin,
12171                size: size(self.block_width, self.line_height),
12172            },
12173            CursorShape::Underline => Bounds {
12174                origin: self.origin
12175                    + origin
12176                    + gpui::Point::new(Pixels::ZERO, self.line_height - px(2.0)),
12177                size: size(self.block_width, px(2.0)),
12178            },
12179        }
12180    }
12181
12182    pub fn layout(
12183        &mut self,
12184        origin: gpui::Point<Pixels>,
12185        cursor_name: Option<CursorName>,
12186        window: &mut Window,
12187        cx: &mut App,
12188    ) {
12189        if let Some(cursor_name) = cursor_name {
12190            let bounds = self.bounds(origin);
12191            let text_size = self.line_height / 1.5;
12192
12193            let name_origin = if cursor_name.is_top_row {
12194                point(bounds.right() - px(1.), bounds.top())
12195            } else {
12196                match self.shape {
12197                    CursorShape::Bar => point(
12198                        bounds.right() - px(2.),
12199                        bounds.top() - text_size / 2. - px(1.),
12200                    ),
12201                    _ => point(
12202                        bounds.right() - px(1.),
12203                        bounds.top() - text_size / 2. - px(1.),
12204                    ),
12205                }
12206            };
12207            let mut name_element = div()
12208                .bg(self.color)
12209                .text_size(text_size)
12210                .px_0p5()
12211                .line_height(text_size + px(2.))
12212                .text_color(cursor_name.color)
12213                .child(cursor_name.string)
12214                .into_any_element();
12215
12216            name_element.prepaint_as_root(name_origin, AvailableSpace::min_size(), window, cx);
12217
12218            self.cursor_name = Some(name_element);
12219        }
12220    }
12221
12222    pub fn paint(&mut self, origin: gpui::Point<Pixels>, window: &mut Window, cx: &mut App) {
12223        let bounds = self.bounds(origin);
12224
12225        //Draw background or border quad
12226        let cursor = if matches!(self.shape, CursorShape::Hollow) {
12227            outline(bounds, self.color, BorderStyle::Solid)
12228        } else {
12229            fill(bounds, self.color)
12230        };
12231
12232        if let Some(name) = &mut self.cursor_name {
12233            name.paint(window, cx);
12234        }
12235
12236        window.paint_quad(cursor);
12237
12238        if let Some(block_text) = &self.block_text {
12239            block_text
12240                .paint(
12241                    self.origin + origin,
12242                    self.line_height,
12243                    TextAlign::Left,
12244                    None,
12245                    window,
12246                    cx,
12247                )
12248                .log_err();
12249        }
12250    }
12251
12252    pub fn shape(&self) -> CursorShape {
12253        self.shape
12254    }
12255}
12256
12257#[derive(Debug)]
12258pub struct HighlightedRange {
12259    pub start_y: Pixels,
12260    pub line_height: Pixels,
12261    pub lines: Vec<HighlightedRangeLine>,
12262    pub color: Hsla,
12263    pub corner_radius: Pixels,
12264}
12265
12266#[derive(Debug)]
12267pub struct HighlightedRangeLine {
12268    pub start_x: Pixels,
12269    pub end_x: Pixels,
12270}
12271
12272impl HighlightedRange {
12273    pub fn paint(&self, fill: bool, bounds: Bounds<Pixels>, window: &mut Window) {
12274        if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
12275            self.paint_lines(self.start_y, &self.lines[0..1], fill, bounds, window);
12276            self.paint_lines(
12277                self.start_y + self.line_height,
12278                &self.lines[1..],
12279                fill,
12280                bounds,
12281                window,
12282            );
12283        } else {
12284            self.paint_lines(self.start_y, &self.lines, fill, bounds, window);
12285        }
12286    }
12287
12288    fn paint_lines(
12289        &self,
12290        start_y: Pixels,
12291        lines: &[HighlightedRangeLine],
12292        fill: bool,
12293        _bounds: Bounds<Pixels>,
12294        window: &mut Window,
12295    ) {
12296        if lines.is_empty() {
12297            return;
12298        }
12299
12300        let first_line = lines.first().unwrap();
12301        let last_line = lines.last().unwrap();
12302
12303        let first_top_left = point(first_line.start_x, start_y);
12304        let first_top_right = point(first_line.end_x, start_y);
12305
12306        let curve_height = point(Pixels::ZERO, self.corner_radius);
12307        let curve_width = |start_x: Pixels, end_x: Pixels| {
12308            let max = (end_x - start_x) / 2.;
12309            let width = if max < self.corner_radius {
12310                max
12311            } else {
12312                self.corner_radius
12313            };
12314
12315            point(width, Pixels::ZERO)
12316        };
12317
12318        let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
12319        let mut builder = if fill {
12320            gpui::PathBuilder::fill()
12321        } else {
12322            gpui::PathBuilder::stroke(px(1.))
12323        };
12324        builder.move_to(first_top_right - top_curve_width);
12325        builder.curve_to(first_top_right + curve_height, first_top_right);
12326
12327        let mut iter = lines.iter().enumerate().peekable();
12328        while let Some((ix, line)) = iter.next() {
12329            let bottom_right = point(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
12330
12331            if let Some((_, next_line)) = iter.peek() {
12332                let next_top_right = point(next_line.end_x, bottom_right.y);
12333
12334                match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
12335                    Ordering::Equal => {
12336                        builder.line_to(bottom_right);
12337                    }
12338                    Ordering::Less => {
12339                        let curve_width = curve_width(next_top_right.x, bottom_right.x);
12340                        builder.line_to(bottom_right - curve_height);
12341                        if self.corner_radius > Pixels::ZERO {
12342                            builder.curve_to(bottom_right - curve_width, bottom_right);
12343                        }
12344                        builder.line_to(next_top_right + curve_width);
12345                        if self.corner_radius > Pixels::ZERO {
12346                            builder.curve_to(next_top_right + curve_height, next_top_right);
12347                        }
12348                    }
12349                    Ordering::Greater => {
12350                        let curve_width = curve_width(bottom_right.x, next_top_right.x);
12351                        builder.line_to(bottom_right - curve_height);
12352                        if self.corner_radius > Pixels::ZERO {
12353                            builder.curve_to(bottom_right + curve_width, bottom_right);
12354                        }
12355                        builder.line_to(next_top_right - curve_width);
12356                        if self.corner_radius > Pixels::ZERO {
12357                            builder.curve_to(next_top_right + curve_height, next_top_right);
12358                        }
12359                    }
12360                }
12361            } else {
12362                let curve_width = curve_width(line.start_x, line.end_x);
12363                builder.line_to(bottom_right - curve_height);
12364                if self.corner_radius > Pixels::ZERO {
12365                    builder.curve_to(bottom_right - curve_width, bottom_right);
12366                }
12367
12368                let bottom_left = point(line.start_x, bottom_right.y);
12369                builder.line_to(bottom_left + curve_width);
12370                if self.corner_radius > Pixels::ZERO {
12371                    builder.curve_to(bottom_left - curve_height, bottom_left);
12372                }
12373            }
12374        }
12375
12376        if first_line.start_x > last_line.start_x {
12377            let curve_width = curve_width(last_line.start_x, first_line.start_x);
12378            let second_top_left = point(last_line.start_x, start_y + self.line_height);
12379            builder.line_to(second_top_left + curve_height);
12380            if self.corner_radius > Pixels::ZERO {
12381                builder.curve_to(second_top_left + curve_width, second_top_left);
12382            }
12383            let first_bottom_left = point(first_line.start_x, second_top_left.y);
12384            builder.line_to(first_bottom_left - curve_width);
12385            if self.corner_radius > Pixels::ZERO {
12386                builder.curve_to(first_bottom_left - curve_height, first_bottom_left);
12387            }
12388        }
12389
12390        builder.line_to(first_top_left + curve_height);
12391        if self.corner_radius > Pixels::ZERO {
12392            builder.curve_to(first_top_left + top_curve_width, first_top_left);
12393        }
12394        builder.line_to(first_top_right - top_curve_width);
12395
12396        if let Ok(path) = builder.build() {
12397            window.paint_path(path, self.color);
12398        }
12399    }
12400}
12401
12402pub(crate) struct StickyHeader {
12403    pub sticky_row: DisplayRow,
12404    pub start_point: Point,
12405    pub offset: ScrollOffset,
12406}
12407
12408enum CursorPopoverType {
12409    CodeContextMenu,
12410    EditPrediction,
12411}
12412
12413pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
12414    (delta.pow(1.2) / 100.0).min(px(3.0)).into()
12415}
12416
12417fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
12418    (delta.pow(1.2) / 300.0).into()
12419}
12420
12421pub fn register_action<T: Action>(
12422    editor: &Entity<Editor>,
12423    window: &mut Window,
12424    listener: impl Fn(&mut Editor, &T, &mut Window, &mut Context<Editor>) + 'static,
12425) {
12426    let editor = editor.clone();
12427    window.on_action(TypeId::of::<T>(), move |action, phase, window, cx| {
12428        let action = action.downcast_ref().unwrap();
12429        if phase == DispatchPhase::Bubble {
12430            editor.update(cx, |editor, cx| {
12431                listener(editor, action, window, cx);
12432            })
12433        }
12434    })
12435}
12436
12437/// Shared between `prepaint` and `compute_auto_height_layout` to ensure
12438/// both full and auto-height editors compute wrap widths consistently.
12439fn calculate_wrap_width(
12440    soft_wrap: SoftWrap,
12441    editor_width: Pixels,
12442    em_width: Pixels,
12443) -> Option<Pixels> {
12444    let wrap_width_for = |column: u32| (column as f32 * em_width).ceil();
12445
12446    match soft_wrap {
12447        SoftWrap::GitDiff => None,
12448        SoftWrap::None => Some(wrap_width_for(MAX_LINE_LEN as u32 / 2)),
12449        SoftWrap::EditorWidth => Some(editor_width),
12450        SoftWrap::Bounded(column) => Some(editor_width.min(wrap_width_for(column))),
12451    }
12452}
12453
12454fn compute_auto_height_layout(
12455    editor: &mut Editor,
12456    min_lines: usize,
12457    max_lines: Option<usize>,
12458    known_dimensions: Size<Option<Pixels>>,
12459    available_width: AvailableSpace,
12460    window: &mut Window,
12461    cx: &mut Context<Editor>,
12462) -> Option<Size<Pixels>> {
12463    let width = known_dimensions.width.or({
12464        if let AvailableSpace::Definite(available_width) = available_width {
12465            Some(available_width)
12466        } else {
12467            None
12468        }
12469    })?;
12470    if let Some(height) = known_dimensions.height {
12471        return Some(size(width, height));
12472    }
12473
12474    let style = editor.style.as_ref().unwrap();
12475    let font_id = window.text_system().resolve_font(&style.text.font());
12476    let font_size = style.text.font_size.to_pixels(window.rem_size());
12477    let line_height = style.text.line_height_in_pixels(window.rem_size());
12478    let em_width = window.text_system().em_width(font_id, font_size).unwrap();
12479
12480    let mut snapshot = editor.snapshot(window, cx);
12481    let gutter_dimensions = snapshot.gutter_dimensions(font_id, font_size, style, window, cx);
12482
12483    editor.gutter_dimensions = gutter_dimensions;
12484    let text_width = width - gutter_dimensions.width;
12485    let overscroll = size(em_width, px(0.));
12486
12487    let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width;
12488    let wrap_width = calculate_wrap_width(editor.soft_wrap_mode(cx), editor_width, em_width)
12489        .map(|width| width.min(editor_width));
12490    if wrap_width.is_some() && editor.set_wrap_width(wrap_width, cx) {
12491        snapshot = editor.snapshot(window, cx);
12492    }
12493
12494    let scroll_height = (snapshot.max_point().row().next_row().0 as f32) * line_height;
12495
12496    let min_height = line_height * min_lines as f32;
12497    let content_height = scroll_height.max(min_height);
12498
12499    let final_height = if let Some(max_lines) = max_lines {
12500        let max_height = line_height * max_lines as f32;
12501        content_height.min(max_height)
12502    } else {
12503        content_height
12504    };
12505
12506    Some(size(width, final_height))
12507}
12508
12509#[cfg(test)]
12510mod tests {
12511    use super::*;
12512    use crate::{
12513        Editor, MultiBuffer, SelectionEffects,
12514        display_map::{BlockPlacement, BlockProperties},
12515        editor_tests::{init_test, update_test_language_settings},
12516    };
12517    use gpui::{TestAppContext, VisualTestContext};
12518    use language::{Buffer, language_settings, tree_sitter_python};
12519    use log::info;
12520    use rand::{RngCore, rngs::StdRng};
12521    use std::num::NonZeroU32;
12522    use util::test::sample_text;
12523
12524    const fn placeholder_hitbox() -> Hitbox {
12525        use gpui::HitboxId;
12526        let zero_bounds = Bounds {
12527            origin: point(Pixels::ZERO, Pixels::ZERO),
12528            size: Size {
12529                width: Pixels::ZERO,
12530                height: Pixels::ZERO,
12531            },
12532        };
12533
12534        Hitbox {
12535            id: HitboxId::placeholder(),
12536            bounds: zero_bounds,
12537            content_mask: ContentMask {
12538                bounds: zero_bounds,
12539            },
12540            behavior: HitboxBehavior::Normal,
12541        }
12542    }
12543
12544    fn test_gutter(line_height: Pixels, snapshot: &EditorSnapshot) -> Gutter<'_> {
12545        const DIMENSIONS: GutterDimensions = GutterDimensions {
12546            left_padding: Pixels::ZERO,
12547            right_padding: Pixels::ZERO,
12548            width: px(30.0),
12549            margin: Pixels::ZERO,
12550            git_blame_entries_width: None,
12551        };
12552        const EMPTY_ROW_INFO: RowInfo = RowInfo {
12553            buffer_id: None,
12554            buffer_row: None,
12555            multibuffer_row: None,
12556            diff_status: None,
12557            expand_info: None,
12558            wrapped_buffer_row: None,
12559        };
12560
12561        const fn row_info(row: u32) -> RowInfo {
12562            RowInfo {
12563                buffer_row: Some(row),
12564                ..EMPTY_ROW_INFO
12565            }
12566        }
12567
12568        const ROW_INFOS: [RowInfo; 6] = [
12569            row_info(0),
12570            row_info(1),
12571            row_info(2),
12572            row_info(3),
12573            row_info(4),
12574            row_info(5),
12575        ];
12576
12577        const HITBOX: Hitbox = placeholder_hitbox();
12578        Gutter {
12579            line_height,
12580            range: DisplayRow(0)..DisplayRow(6),
12581            scroll_position: gpui::Point::default(),
12582            dimensions: &DIMENSIONS,
12583            hitbox: &HITBOX,
12584            snapshot: snapshot,
12585            row_infos: &ROW_INFOS,
12586        }
12587    }
12588
12589    #[gpui::test]
12590    async fn test_soft_wrap_editor_width_auto_height_editor(cx: &mut TestAppContext) {
12591        init_test(cx, |_| {});
12592        let window = cx.add_window(|window, cx| {
12593            let buffer = MultiBuffer::build_simple(&"a ".to_string().repeat(100), cx);
12594            let mut editor = Editor::new(
12595                EditorMode::AutoHeight {
12596                    min_lines: 1,
12597                    max_lines: None,
12598                },
12599                buffer,
12600                None,
12601                window,
12602                cx,
12603            );
12604            editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
12605            editor
12606        });
12607        let cx = &mut VisualTestContext::from_window(*window, cx);
12608        let editor = window.root(cx).unwrap();
12609        let style = cx.update(|_, cx| editor.update(cx, |editor, cx| editor.style(cx).clone()));
12610
12611        for x in 1..=100 {
12612            let (_, state) = cx.draw(
12613                Default::default(),
12614                size(px(200. + 0.13 * x as f32), px(500.)),
12615                |_, _| EditorElement::new(&editor, style.clone()),
12616            );
12617
12618            assert!(
12619                state.position_map.scroll_max.x == 0.,
12620                "Soft wrapped editor should have no horizontal scrolling!"
12621            );
12622        }
12623    }
12624
12625    #[gpui::test]
12626    async fn test_soft_wrap_editor_width_full_editor(cx: &mut TestAppContext) {
12627        init_test(cx, |_| {});
12628        let window = cx.add_window(|window, cx| {
12629            let buffer = MultiBuffer::build_simple(&"a ".to_string().repeat(100), cx);
12630            let mut editor = Editor::new(EditorMode::full(), buffer, None, window, cx);
12631            editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
12632            editor
12633        });
12634        let cx = &mut VisualTestContext::from_window(*window, cx);
12635        let editor = window.root(cx).unwrap();
12636        let style = cx.update(|_, cx| editor.update(cx, |editor, cx| editor.style(cx).clone()));
12637
12638        for x in 1..=100 {
12639            let (_, state) = cx.draw(
12640                Default::default(),
12641                size(px(200. + 0.13 * x as f32), px(500.)),
12642                |_, _| EditorElement::new(&editor, style.clone()),
12643            );
12644
12645            assert!(
12646                state.position_map.scroll_max.x == 0.,
12647                "Soft wrapped editor should have no horizontal scrolling!"
12648            );
12649        }
12650    }
12651
12652    #[gpui::test]
12653    fn test_layout_line_numbers(cx: &mut TestAppContext) {
12654        init_test(cx, |_| {});
12655        let window = cx.add_window(|window, cx| {
12656            let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
12657            Editor::new(EditorMode::full(), buffer, None, window, cx)
12658        });
12659
12660        let editor = window.root(cx).unwrap();
12661        let style = editor.update(cx, |editor, cx| editor.style(cx).clone());
12662        let line_height = window
12663            .update(cx, |_, window, _| {
12664                style.text.line_height_in_pixels(window.rem_size())
12665            })
12666            .unwrap();
12667        let element = EditorElement::new(&editor, style);
12668        let snapshot = window
12669            .update(cx, |editor, window, cx| editor.snapshot(window, cx))
12670            .unwrap();
12671
12672        let layouts = cx
12673            .update_window(*window, |_, window, cx| {
12674                element.layout_line_numbers(
12675                    &test_gutter(line_height, &snapshot),
12676                    &BTreeMap::default(),
12677                    Some(DisplayRow(0)),
12678                    window,
12679                    cx,
12680                )
12681            })
12682            .unwrap();
12683        assert_eq!(layouts.len(), 6);
12684
12685        let relative_rows = window
12686            .update(cx, |editor, window, cx| {
12687                let snapshot = editor.snapshot(window, cx);
12688                snapshot.calculate_relative_line_numbers(
12689                    &(DisplayRow(0)..DisplayRow(6)),
12690                    DisplayRow(3),
12691                    false,
12692                )
12693            })
12694            .unwrap();
12695        assert_eq!(relative_rows[&DisplayRow(0)], 3);
12696        assert_eq!(relative_rows[&DisplayRow(1)], 2);
12697        assert_eq!(relative_rows[&DisplayRow(2)], 1);
12698        // current line has no relative number
12699        assert!(!relative_rows.contains_key(&DisplayRow(3)));
12700        assert_eq!(relative_rows[&DisplayRow(4)], 1);
12701        assert_eq!(relative_rows[&DisplayRow(5)], 2);
12702
12703        // works if cursor is before screen
12704        let relative_rows = window
12705            .update(cx, |editor, window, cx| {
12706                let snapshot = editor.snapshot(window, cx);
12707                snapshot.calculate_relative_line_numbers(
12708                    &(DisplayRow(3)..DisplayRow(6)),
12709                    DisplayRow(1),
12710                    false,
12711                )
12712            })
12713            .unwrap();
12714        assert_eq!(relative_rows.len(), 3);
12715        assert_eq!(relative_rows[&DisplayRow(3)], 2);
12716        assert_eq!(relative_rows[&DisplayRow(4)], 3);
12717        assert_eq!(relative_rows[&DisplayRow(5)], 4);
12718
12719        // works if cursor is after screen
12720        let relative_rows = window
12721            .update(cx, |editor, window, cx| {
12722                let snapshot = editor.snapshot(window, cx);
12723                snapshot.calculate_relative_line_numbers(
12724                    &(DisplayRow(0)..DisplayRow(3)),
12725                    DisplayRow(6),
12726                    false,
12727                )
12728            })
12729            .unwrap();
12730        assert_eq!(relative_rows.len(), 3);
12731        assert_eq!(relative_rows[&DisplayRow(0)], 5);
12732        assert_eq!(relative_rows[&DisplayRow(1)], 4);
12733        assert_eq!(relative_rows[&DisplayRow(2)], 3);
12734
12735        let gutter = Gutter {
12736            row_infos: &(0..6)
12737                .map(|row| RowInfo {
12738                    buffer_row: Some(row),
12739                    diff_status: (row == DELETED_LINE).then(|| {
12740                        DiffHunkStatus::deleted(
12741                            buffer_diff::DiffHunkSecondaryStatus::NoSecondaryHunk,
12742                        )
12743                    }),
12744                    ..Default::default()
12745                })
12746                .collect::<Vec<_>>(),
12747            ..test_gutter(line_height, &snapshot)
12748        };
12749
12750        const DELETED_LINE: u32 = 3;
12751        let layouts = cx
12752            .update_window(*window, |_, window, cx| {
12753                element.layout_line_numbers(
12754                    &gutter,
12755                    &BTreeMap::default(),
12756                    Some(DisplayRow(0)),
12757                    window,
12758                    cx,
12759                )
12760            })
12761            .unwrap();
12762        assert_eq!(layouts.len(), 5,);
12763        assert!(
12764            layouts.get(&MultiBufferRow(DELETED_LINE)).is_none(),
12765            "Deleted line should not have a line number"
12766        );
12767    }
12768
12769    #[gpui::test]
12770    async fn test_layout_line_numbers_with_folded_lines(cx: &mut TestAppContext) {
12771        init_test(cx, |_| {});
12772
12773        let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
12774
12775        let window = cx.add_window(|window, cx| {
12776            let buffer = cx.new(|cx| {
12777                Buffer::local(
12778                    indoc::indoc! {"
12779                        fn test() -> int {
12780                            return 2;
12781                        }
12782
12783                        fn another_test() -> int {
12784                            # This is a very peculiar method that is hard to grasp.
12785                            return 4;
12786                        }
12787                    "},
12788                    cx,
12789                )
12790                .with_language(python_lang, cx)
12791            });
12792
12793            let buffer = MultiBuffer::build_from_buffer(buffer, cx);
12794            Editor::new(EditorMode::full(), buffer, None, window, cx)
12795        });
12796
12797        let editor = window.root(cx).unwrap();
12798        let style = editor.update(cx, |editor, cx| editor.style(cx).clone());
12799        let line_height = window
12800            .update(cx, |_, window, _| {
12801                style.text.line_height_in_pixels(window.rem_size())
12802            })
12803            .unwrap();
12804        let element = EditorElement::new(&editor, style);
12805        let snapshot = window
12806            .update(cx, |editor, window, cx| {
12807                editor.fold_at(MultiBufferRow(0), window, cx);
12808                editor.snapshot(window, cx)
12809            })
12810            .unwrap();
12811
12812        let layouts = cx
12813            .update_window(*window, |_, window, cx| {
12814                element.layout_line_numbers(
12815                    &test_gutter(line_height, &snapshot),
12816                    &BTreeMap::default(),
12817                    Some(DisplayRow(3)),
12818                    window,
12819                    cx,
12820                )
12821            })
12822            .unwrap();
12823        assert_eq!(layouts.len(), 6);
12824
12825        let relative_rows = window
12826            .update(cx, |editor, window, cx| {
12827                let snapshot = editor.snapshot(window, cx);
12828                snapshot.calculate_relative_line_numbers(
12829                    &(DisplayRow(0)..DisplayRow(6)),
12830                    DisplayRow(3),
12831                    false,
12832                )
12833            })
12834            .unwrap();
12835        assert_eq!(relative_rows[&DisplayRow(0)], 3);
12836        assert_eq!(relative_rows[&DisplayRow(1)], 2);
12837        assert_eq!(relative_rows[&DisplayRow(2)], 1);
12838        // current line has no relative number
12839        assert!(!relative_rows.contains_key(&DisplayRow(3)));
12840        assert_eq!(relative_rows[&DisplayRow(4)], 1);
12841        assert_eq!(relative_rows[&DisplayRow(5)], 2);
12842    }
12843
12844    #[gpui::test]
12845    fn test_layout_line_numbers_wrapping(cx: &mut TestAppContext) {
12846        init_test(cx, |_| {});
12847        let window = cx.add_window(|window, cx| {
12848            let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
12849            Editor::new(EditorMode::full(), buffer, None, window, cx)
12850        });
12851
12852        update_test_language_settings(cx, &|s| {
12853            s.defaults.preferred_line_length = Some(5_u32);
12854            s.defaults.soft_wrap = Some(language_settings::SoftWrap::Bounded);
12855        });
12856
12857        let editor = window.root(cx).unwrap();
12858        let style = editor.update(cx, |editor, cx| editor.style(cx).clone());
12859        let line_height = window
12860            .update(cx, |_, window, _| {
12861                style.text.line_height_in_pixels(window.rem_size())
12862            })
12863            .unwrap();
12864        let element = EditorElement::new(&editor, style);
12865        let snapshot = window
12866            .update(cx, |editor, window, cx| editor.snapshot(window, cx))
12867            .unwrap();
12868
12869        let layouts = cx
12870            .update_window(*window, |_, window, cx| {
12871                element.layout_line_numbers(
12872                    &test_gutter(line_height, &snapshot),
12873                    &BTreeMap::default(),
12874                    Some(DisplayRow(0)),
12875                    window,
12876                    cx,
12877                )
12878            })
12879            .unwrap();
12880        assert_eq!(layouts.len(), 3);
12881
12882        let relative_rows = window
12883            .update(cx, |editor, window, cx| {
12884                let snapshot = editor.snapshot(window, cx);
12885                snapshot.calculate_relative_line_numbers(
12886                    &(DisplayRow(0)..DisplayRow(6)),
12887                    DisplayRow(3),
12888                    true,
12889                )
12890            })
12891            .unwrap();
12892
12893        assert_eq!(relative_rows[&DisplayRow(0)], 3);
12894        assert_eq!(relative_rows[&DisplayRow(1)], 2);
12895        assert_eq!(relative_rows[&DisplayRow(2)], 1);
12896        // current line has no relative number
12897        assert!(!relative_rows.contains_key(&DisplayRow(3)));
12898        assert_eq!(relative_rows[&DisplayRow(4)], 1);
12899        assert_eq!(relative_rows[&DisplayRow(5)], 2);
12900
12901        let layouts = cx
12902            .update_window(*window, |_, window, cx| {
12903                element.layout_line_numbers(
12904                    &Gutter {
12905                        row_infos: &(0..6)
12906                            .map(|row| RowInfo {
12907                                buffer_row: Some(row),
12908                                diff_status: Some(DiffHunkStatus::deleted(
12909                                    buffer_diff::DiffHunkSecondaryStatus::NoSecondaryHunk,
12910                                )),
12911                                ..Default::default()
12912                            })
12913                            .collect::<Vec<_>>(),
12914                        ..test_gutter(line_height, &snapshot)
12915                    },
12916                    &BTreeMap::from_iter([(DisplayRow(0), LineHighlightSpec::default())]),
12917                    Some(DisplayRow(0)),
12918                    window,
12919                    cx,
12920                )
12921            })
12922            .unwrap();
12923        assert!(
12924            layouts.is_empty(),
12925            "Deleted lines should have no line number"
12926        );
12927
12928        let relative_rows = window
12929            .update(cx, |editor, window, cx| {
12930                let snapshot = editor.snapshot(window, cx);
12931                snapshot.calculate_relative_line_numbers(
12932                    &(DisplayRow(0)..DisplayRow(6)),
12933                    DisplayRow(3),
12934                    true,
12935                )
12936            })
12937            .unwrap();
12938
12939        // Deleted lines should still have relative numbers
12940        assert_eq!(relative_rows[&DisplayRow(0)], 3);
12941        assert_eq!(relative_rows[&DisplayRow(1)], 2);
12942        assert_eq!(relative_rows[&DisplayRow(2)], 1);
12943        // current line, even if deleted, has no relative number
12944        assert!(!relative_rows.contains_key(&DisplayRow(3)));
12945        assert_eq!(relative_rows[&DisplayRow(4)], 1);
12946        assert_eq!(relative_rows[&DisplayRow(5)], 2);
12947    }
12948
12949    #[gpui::test]
12950    async fn test_vim_visual_selections(cx: &mut TestAppContext) {
12951        init_test(cx, |_| {});
12952
12953        let window = cx.add_window(|window, cx| {
12954            let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
12955            Editor::new(EditorMode::full(), buffer, None, window, cx)
12956        });
12957        let cx = &mut VisualTestContext::from_window(*window, cx);
12958        let editor = window.root(cx).unwrap();
12959        let style = cx.update(|_, cx| editor.update(cx, |editor, cx| editor.style(cx).clone()));
12960
12961        window
12962            .update(cx, |editor, window, cx| {
12963                editor.cursor_offset_on_selection = true;
12964                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12965                    s.select_ranges([
12966                        Point::new(0, 0)..Point::new(1, 0),
12967                        Point::new(3, 2)..Point::new(3, 3),
12968                        Point::new(5, 6)..Point::new(6, 0),
12969                    ]);
12970                });
12971            })
12972            .unwrap();
12973
12974        let (_, state) = cx.draw(
12975            point(px(500.), px(500.)),
12976            size(px(500.), px(500.)),
12977            |_, _| EditorElement::new(&editor, style),
12978        );
12979
12980        assert_eq!(state.selections.len(), 1);
12981        let local_selections = &state.selections[0].1;
12982        assert_eq!(local_selections.len(), 3);
12983        // moves cursor back one line
12984        assert_eq!(
12985            local_selections[0].head,
12986            DisplayPoint::new(DisplayRow(0), 6)
12987        );
12988        assert_eq!(
12989            local_selections[0].range,
12990            DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0)
12991        );
12992
12993        // moves cursor back one column
12994        assert_eq!(
12995            local_selections[1].range,
12996            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 3)
12997        );
12998        assert_eq!(
12999            local_selections[1].head,
13000            DisplayPoint::new(DisplayRow(3), 2)
13001        );
13002
13003        // leaves cursor on the max point
13004        assert_eq!(
13005            local_selections[2].range,
13006            DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(6), 0)
13007        );
13008        assert_eq!(
13009            local_selections[2].head,
13010            DisplayPoint::new(DisplayRow(6), 0)
13011        );
13012
13013        // active lines does not include 1 (even though the range of the selection does)
13014        assert_eq!(
13015            state.active_rows.keys().cloned().collect::<Vec<_>>(),
13016            vec![DisplayRow(0), DisplayRow(3), DisplayRow(5), DisplayRow(6)]
13017        );
13018    }
13019
13020    #[gpui::test]
13021    fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
13022        init_test(cx, |_| {});
13023
13024        let window = cx.add_window(|window, cx| {
13025            let buffer = MultiBuffer::build_simple("", cx);
13026            Editor::new(EditorMode::full(), buffer, None, window, cx)
13027        });
13028        let cx = &mut VisualTestContext::from_window(*window, cx);
13029        let editor = window.root(cx).unwrap();
13030        let style = cx.update(|_, cx| editor.update(cx, |editor, cx| editor.style(cx).clone()));
13031        window
13032            .update(cx, |editor, window, cx| {
13033                editor.set_placeholder_text("hello", window, cx);
13034                editor.insert_blocks(
13035                    [BlockProperties {
13036                        style: BlockStyle::Fixed,
13037                        placement: BlockPlacement::Above(Anchor::Min),
13038                        height: Some(3),
13039                        render: Arc::new(|cx| div().h(3. * cx.window.line_height()).into_any()),
13040                        priority: 0,
13041                    }],
13042                    None,
13043                    cx,
13044                );
13045
13046                // Blur the editor so that it displays placeholder text.
13047                window.blur();
13048            })
13049            .unwrap();
13050
13051        let (_, state) = cx.draw(
13052            point(px(500.), px(500.)),
13053            size(px(500.), px(500.)),
13054            |_, _| EditorElement::new(&editor, style),
13055        );
13056        assert_eq!(state.position_map.line_layouts.len(), 4);
13057        assert_eq!(state.line_numbers.len(), 1);
13058        assert_eq!(
13059            state
13060                .line_numbers
13061                .get(&MultiBufferRow(0))
13062                .map(|line_number| line_number
13063                    .segments
13064                    .first()
13065                    .unwrap()
13066                    .shaped_line
13067                    .text
13068                    .as_ref()),
13069            Some("1")
13070        );
13071    }
13072
13073    #[gpui::test]
13074    fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
13075        const TAB_SIZE: u32 = 4;
13076
13077        let input_text = "\t \t|\t| a b";
13078        let expected_invisibles = vec![
13079            Invisible::Tab {
13080                line_start_offset: 0,
13081                line_end_offset: TAB_SIZE as usize,
13082            },
13083            Invisible::Whitespace {
13084                line_offset: TAB_SIZE as usize,
13085            },
13086            Invisible::Tab {
13087                line_start_offset: TAB_SIZE as usize + 1,
13088                line_end_offset: TAB_SIZE as usize * 2,
13089            },
13090            Invisible::Tab {
13091                line_start_offset: TAB_SIZE as usize * 2 + 1,
13092                line_end_offset: TAB_SIZE as usize * 3,
13093            },
13094            Invisible::Whitespace {
13095                line_offset: TAB_SIZE as usize * 3 + 1,
13096            },
13097            Invisible::Whitespace {
13098                line_offset: TAB_SIZE as usize * 3 + 3,
13099            },
13100        ];
13101        assert_eq!(
13102            expected_invisibles.len(),
13103            input_text
13104                .chars()
13105                .filter(|initial_char| initial_char.is_whitespace())
13106                .count(),
13107            "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
13108        );
13109
13110        for show_line_numbers in [true, false] {
13111            init_test(cx, |s| {
13112                s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
13113                s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
13114            });
13115
13116            let actual_invisibles = collect_invisibles_from_new_editor(
13117                cx,
13118                EditorMode::full(),
13119                input_text,
13120                px(500.0),
13121                show_line_numbers,
13122            );
13123
13124            assert_eq!(expected_invisibles, actual_invisibles);
13125        }
13126    }
13127
13128    #[gpui::test]
13129    fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
13130        init_test(cx, |s| {
13131            s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
13132            s.defaults.tab_size = NonZeroU32::new(4);
13133        });
13134
13135        for editor_mode_without_invisibles in [
13136            EditorMode::SingleLine,
13137            EditorMode::AutoHeight {
13138                min_lines: 1,
13139                max_lines: Some(100),
13140            },
13141        ] {
13142            for show_line_numbers in [true, false] {
13143                let invisibles = collect_invisibles_from_new_editor(
13144                    cx,
13145                    editor_mode_without_invisibles.clone(),
13146                    "\t\t\t| | a b",
13147                    px(500.0),
13148                    show_line_numbers,
13149                );
13150                assert!(
13151                    invisibles.is_empty(),
13152                    "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}"
13153                );
13154            }
13155        }
13156    }
13157
13158    #[gpui::test]
13159    fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
13160        let tab_size = 4;
13161        let input_text = "a\tbcd     ".repeat(9);
13162        let repeated_invisibles = [
13163            Invisible::Tab {
13164                line_start_offset: 1,
13165                line_end_offset: tab_size as usize,
13166            },
13167            Invisible::Whitespace {
13168                line_offset: tab_size as usize + 3,
13169            },
13170            Invisible::Whitespace {
13171                line_offset: tab_size as usize + 4,
13172            },
13173            Invisible::Whitespace {
13174                line_offset: tab_size as usize + 5,
13175            },
13176            Invisible::Whitespace {
13177                line_offset: tab_size as usize + 6,
13178            },
13179            Invisible::Whitespace {
13180                line_offset: tab_size as usize + 7,
13181            },
13182        ];
13183        let expected_invisibles = std::iter::once(repeated_invisibles)
13184            .cycle()
13185            .take(9)
13186            .flatten()
13187            .collect::<Vec<_>>();
13188        assert_eq!(
13189            expected_invisibles.len(),
13190            input_text
13191                .chars()
13192                .filter(|initial_char| initial_char.is_whitespace())
13193                .count(),
13194            "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
13195        );
13196        info!("Expected invisibles: {expected_invisibles:?}");
13197
13198        init_test(cx, |_| {});
13199
13200        // Put the same string with repeating whitespace pattern into editors of various size,
13201        // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
13202        let resize_step = 10.0;
13203        let mut editor_width = 200.0;
13204        while editor_width <= 1000.0 {
13205            for show_line_numbers in [true, false] {
13206                update_test_language_settings(cx, &|s| {
13207                    s.defaults.tab_size = NonZeroU32::new(tab_size);
13208                    s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
13209                    s.defaults.preferred_line_length = Some(editor_width as u32);
13210                    s.defaults.soft_wrap = Some(language_settings::SoftWrap::Bounded);
13211                });
13212
13213                let actual_invisibles = collect_invisibles_from_new_editor(
13214                    cx,
13215                    EditorMode::full(),
13216                    &input_text,
13217                    px(editor_width),
13218                    show_line_numbers,
13219                );
13220
13221                // Whatever the editor size is, ensure it has the same invisible kinds in the same order
13222                // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
13223                let mut i = 0;
13224                for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
13225                    i = actual_index;
13226                    match expected_invisibles.get(i) {
13227                        Some(expected_invisible) => match (expected_invisible, actual_invisible) {
13228                            (Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
13229                            | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
13230                            _ => {
13231                                panic!(
13232                                    "At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}"
13233                                )
13234                            }
13235                        },
13236                        None => {
13237                            panic!("Unexpected extra invisible {actual_invisible:?} at index {i}")
13238                        }
13239                    }
13240                }
13241                let missing_expected_invisibles = &expected_invisibles[i + 1..];
13242                assert!(
13243                    missing_expected_invisibles.is_empty(),
13244                    "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
13245                );
13246
13247                editor_width += resize_step;
13248            }
13249        }
13250    }
13251
13252    fn collect_invisibles_from_new_editor(
13253        cx: &mut TestAppContext,
13254        editor_mode: EditorMode,
13255        input_text: &str,
13256        editor_width: Pixels,
13257        show_line_numbers: bool,
13258    ) -> Vec<Invisible> {
13259        info!(
13260            "Creating editor with mode {editor_mode:?}, width {}px and text '{input_text}'",
13261            f32::from(editor_width)
13262        );
13263        let window = cx.add_window(|window, cx| {
13264            let buffer = MultiBuffer::build_simple(input_text, cx);
13265            Editor::new(editor_mode, buffer, None, window, cx)
13266        });
13267        let cx = &mut VisualTestContext::from_window(*window, cx);
13268        let editor = window.root(cx).unwrap();
13269
13270        let style = editor.update(cx, |editor, cx| editor.style(cx).clone());
13271        window
13272            .update(cx, |editor, _, cx| {
13273                editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
13274                editor.set_wrap_width(Some(editor_width), cx);
13275                editor.set_show_line_numbers(show_line_numbers, cx);
13276            })
13277            .unwrap();
13278        let (_, state) = cx.draw(
13279            point(px(500.), px(500.)),
13280            size(px(500.), px(500.)),
13281            |_, _| EditorElement::new(&editor, style),
13282        );
13283        state
13284            .position_map
13285            .line_layouts
13286            .iter()
13287            .flat_map(|line_with_invisibles| &line_with_invisibles.invisibles)
13288            .cloned()
13289            .collect()
13290    }
13291
13292    #[gpui::test]
13293    fn test_merge_overlapping_ranges() {
13294        let base_bg = Hsla::white();
13295        let color1 = Hsla {
13296            h: 0.0,
13297            s: 0.5,
13298            l: 0.5,
13299            a: 0.5,
13300        };
13301        let color2 = Hsla {
13302            h: 120.0,
13303            s: 0.5,
13304            l: 0.5,
13305            a: 0.5,
13306        };
13307
13308        let display_point = |col| DisplayPoint::new(DisplayRow(0), col);
13309        let cols = |v: &Vec<(Range<DisplayPoint>, Hsla)>| -> Vec<(u32, u32)> {
13310            v.iter()
13311                .map(|(r, _)| (r.start.column(), r.end.column()))
13312                .collect()
13313        };
13314
13315        // Test overlapping ranges blend colors
13316        let overlapping = vec![
13317            (display_point(5)..display_point(15), color1),
13318            (display_point(10)..display_point(20), color2),
13319        ];
13320        let result = EditorElement::merge_overlapping_ranges(overlapping, base_bg);
13321        assert_eq!(cols(&result), vec![(5, 10), (10, 15), (15, 20)]);
13322
13323        // Test middle segment should have blended color
13324        let blended = Hsla::blend(Hsla::blend(base_bg, color1), color2);
13325        assert_eq!(result[1].1, blended);
13326
13327        // Test adjacent same-color ranges merge
13328        let adjacent_same = vec![
13329            (display_point(5)..display_point(10), color1),
13330            (display_point(10)..display_point(15), color1),
13331        ];
13332        let result = EditorElement::merge_overlapping_ranges(adjacent_same, base_bg);
13333        assert_eq!(cols(&result), vec![(5, 15)]);
13334
13335        // Test contained range splits
13336        let contained = vec![
13337            (display_point(5)..display_point(20), color1),
13338            (display_point(10)..display_point(15), color2),
13339        ];
13340        let result = EditorElement::merge_overlapping_ranges(contained, base_bg);
13341        assert_eq!(cols(&result), vec![(5, 10), (10, 15), (15, 20)]);
13342
13343        // Test multiple overlaps split at every boundary
13344        let color3 = Hsla {
13345            h: 240.0,
13346            s: 0.5,
13347            l: 0.5,
13348            a: 0.5,
13349        };
13350        let complex = vec![
13351            (display_point(5)..display_point(12), color1),
13352            (display_point(8)..display_point(16), color2),
13353            (display_point(10)..display_point(14), color3),
13354        ];
13355        let result = EditorElement::merge_overlapping_ranges(complex, base_bg);
13356        assert_eq!(
13357            cols(&result),
13358            vec![(5, 8), (8, 10), (10, 12), (12, 14), (14, 16)]
13359        );
13360    }
13361
13362    #[gpui::test]
13363    fn test_bg_segments_per_row() {
13364        let base_bg = Hsla::white();
13365
13366        // Case A: selection spans three display rows: row 1 [5, end), full row 2, row 3 [0, 7)
13367        {
13368            let selection_color = Hsla {
13369                h: 200.0,
13370                s: 0.5,
13371                l: 0.5,
13372                a: 0.5,
13373            };
13374            let player_color = PlayerColor {
13375                cursor: selection_color,
13376                background: selection_color,
13377                selection: selection_color,
13378            };
13379
13380            let spanning_selection = SelectionLayout {
13381                head: DisplayPoint::new(DisplayRow(3), 7),
13382                cursor_shape: CursorShape::Bar,
13383                is_newest: true,
13384                is_local: true,
13385                range: DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(3), 7),
13386                active_rows: DisplayRow(1)..DisplayRow(4),
13387                user_name: None,
13388            };
13389
13390            let selections = vec![(player_color, vec![spanning_selection])];
13391            let result = EditorElement::bg_segments_per_row(
13392                DisplayRow(0)..DisplayRow(5),
13393                &selections,
13394                &[],
13395                base_bg,
13396            );
13397
13398            assert_eq!(result.len(), 5);
13399            assert!(result[0].is_empty());
13400            assert_eq!(result[1].len(), 1);
13401            assert_eq!(result[2].len(), 1);
13402            assert_eq!(result[3].len(), 1);
13403            assert!(result[4].is_empty());
13404
13405            assert_eq!(result[1][0].0.start, DisplayPoint::new(DisplayRow(1), 5));
13406            assert_eq!(result[1][0].0.end.row(), DisplayRow(1));
13407            assert_eq!(result[1][0].0.end.column(), u32::MAX);
13408            assert_eq!(result[2][0].0.start, DisplayPoint::new(DisplayRow(2), 0));
13409            assert_eq!(result[2][0].0.end.row(), DisplayRow(2));
13410            assert_eq!(result[2][0].0.end.column(), u32::MAX);
13411            assert_eq!(result[3][0].0.start, DisplayPoint::new(DisplayRow(3), 0));
13412            assert_eq!(result[3][0].0.end, DisplayPoint::new(DisplayRow(3), 7));
13413        }
13414
13415        // Case B: selection ends exactly at the start of row 3, excluding row 3
13416        {
13417            let selection_color = Hsla {
13418                h: 120.0,
13419                s: 0.5,
13420                l: 0.5,
13421                a: 0.5,
13422            };
13423            let player_color = PlayerColor {
13424                cursor: selection_color,
13425                background: selection_color,
13426                selection: selection_color,
13427            };
13428
13429            let selection = SelectionLayout {
13430                head: DisplayPoint::new(DisplayRow(2), 0),
13431                cursor_shape: CursorShape::Bar,
13432                is_newest: true,
13433                is_local: true,
13434                range: DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(3), 0),
13435                active_rows: DisplayRow(1)..DisplayRow(3),
13436                user_name: None,
13437            };
13438
13439            let selections = vec![(player_color, vec![selection])];
13440            let result = EditorElement::bg_segments_per_row(
13441                DisplayRow(0)..DisplayRow(4),
13442                &selections,
13443                &[],
13444                base_bg,
13445            );
13446
13447            assert_eq!(result.len(), 4);
13448            assert!(result[0].is_empty());
13449            assert_eq!(result[1].len(), 1);
13450            assert_eq!(result[2].len(), 1);
13451            assert!(result[3].is_empty());
13452
13453            assert_eq!(result[1][0].0.start, DisplayPoint::new(DisplayRow(1), 5));
13454            assert_eq!(result[1][0].0.end.row(), DisplayRow(1));
13455            assert_eq!(result[1][0].0.end.column(), u32::MAX);
13456            assert_eq!(result[2][0].0.start, DisplayPoint::new(DisplayRow(2), 0));
13457            assert_eq!(result[2][0].0.end.row(), DisplayRow(2));
13458            assert_eq!(result[2][0].0.end.column(), u32::MAX);
13459        }
13460    }
13461
13462    #[cfg(test)]
13463    fn generate_test_run(len: usize, color: Hsla) -> TextRun {
13464        TextRun {
13465            len,
13466            color,
13467            ..Default::default()
13468        }
13469    }
13470
13471    #[gpui::test]
13472    fn test_split_runs_by_bg_segments(cx: &mut gpui::TestAppContext) {
13473        init_test(cx, |_| {});
13474
13475        let dx = |start: u32, end: u32| {
13476            DisplayPoint::new(DisplayRow(0), start)..DisplayPoint::new(DisplayRow(0), end)
13477        };
13478
13479        let text_color = Hsla {
13480            h: 210.0,
13481            s: 0.1,
13482            l: 0.4,
13483            a: 1.0,
13484        };
13485        let bg_1 = Hsla {
13486            h: 30.0,
13487            s: 0.6,
13488            l: 0.8,
13489            a: 1.0,
13490        };
13491        let bg_2 = Hsla {
13492            h: 200.0,
13493            s: 0.6,
13494            l: 0.2,
13495            a: 1.0,
13496        };
13497        let min_contrast = 45.0;
13498        let adjusted_bg1 = ensure_minimum_contrast(text_color, bg_1, min_contrast);
13499        let adjusted_bg2 = ensure_minimum_contrast(text_color, bg_2, min_contrast);
13500
13501        // Case A: single run; disjoint segments inside the run
13502        {
13503            let runs = vec![generate_test_run(20, text_color)];
13504            let segs = vec![(dx(5, 10), bg_1), (dx(12, 16), bg_2)];
13505            let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 0);
13506            // Expected slices: [0,5) [5,10) [10,12) [12,16) [16,20)
13507            assert_eq!(
13508                out.iter().map(|r| r.len).collect::<Vec<_>>(),
13509                vec![5, 5, 2, 4, 4]
13510            );
13511            assert_eq!(out[0].color, text_color);
13512            assert_eq!(out[1].color, adjusted_bg1);
13513            assert_eq!(out[2].color, text_color);
13514            assert_eq!(out[3].color, adjusted_bg2);
13515            assert_eq!(out[4].color, text_color);
13516        }
13517
13518        // Case B: multiple runs; segment extends to end of line (u32::MAX)
13519        {
13520            let runs = vec![
13521                generate_test_run(8, text_color),
13522                generate_test_run(7, text_color),
13523            ];
13524            let segs = vec![(dx(6, u32::MAX), bg_1)];
13525            let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 0);
13526            // Expected slices across runs: [0,6) [6,8) | [0,7)
13527            assert_eq!(out.iter().map(|r| r.len).collect::<Vec<_>>(), vec![6, 2, 7]);
13528            assert_eq!(out[0].color, text_color);
13529            assert_eq!(out[1].color, adjusted_bg1);
13530            assert_eq!(out[2].color, adjusted_bg1);
13531        }
13532
13533        // Case C: multi-byte characters
13534        {
13535            // for text: "Hello 🌍 δΈ–η•Œ!"
13536            let runs = vec![
13537                generate_test_run(5, text_color), // "Hello"
13538                generate_test_run(6, text_color), // " 🌍 "
13539                generate_test_run(6, text_color), // "δΈ–η•Œ"
13540                generate_test_run(1, text_color), // "!"
13541            ];
13542            // selecting "🌍 δΈ–"
13543            let segs = vec![(dx(6, 14), bg_1)];
13544            let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 0);
13545            // "Hello" | " " | "🌍 " | "δΈ–" | "η•Œ" | "!"
13546            assert_eq!(
13547                out.iter().map(|r| r.len).collect::<Vec<_>>(),
13548                vec![5, 1, 5, 3, 3, 1]
13549            );
13550            assert_eq!(out[0].color, text_color); // "Hello"
13551            assert_eq!(out[2].color, adjusted_bg1); // "🌍 "
13552            assert_eq!(out[3].color, adjusted_bg1); // "δΈ–"
13553            assert_eq!(out[4].color, text_color); // "η•Œ"
13554            assert_eq!(out[5].color, text_color); // "!"
13555        }
13556
13557        // Case D: split multiple consecutive text runs with segments
13558        {
13559            let segs = vec![
13560                (dx(2, 4), bg_1),   // selecting "cd"
13561                (dx(4, 8), bg_2),   // selecting "efgh"
13562                (dx(9, 11), bg_1),  // selecting "jk"
13563                (dx(12, 16), bg_2), // selecting "mnop"
13564                (dx(18, 19), bg_1), // selecting "s"
13565            ];
13566
13567            // for text: "abcdef"
13568            let runs = vec![
13569                generate_test_run(2, text_color), // ab
13570                generate_test_run(4, text_color), // cdef
13571            ];
13572            let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 0);
13573            // new splits "ab", "cd", "ef"
13574            assert_eq!(out.iter().map(|r| r.len).collect::<Vec<_>>(), vec![2, 2, 2]);
13575            assert_eq!(out[0].color, text_color);
13576            assert_eq!(out[1].color, adjusted_bg1);
13577            assert_eq!(out[2].color, adjusted_bg2);
13578
13579            // for text: "ghijklmn"
13580            let runs = vec![
13581                generate_test_run(3, text_color), // ghi
13582                generate_test_run(2, text_color), // jk
13583                generate_test_run(3, text_color), // lmn
13584            ];
13585            let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 6); // 2 + 4 from first run
13586            // new splits "gh", "i", "jk", "l", "mn"
13587            assert_eq!(
13588                out.iter().map(|r| r.len).collect::<Vec<_>>(),
13589                vec![2, 1, 2, 1, 2]
13590            );
13591            assert_eq!(out[0].color, adjusted_bg2);
13592            assert_eq!(out[1].color, text_color);
13593            assert_eq!(out[2].color, adjusted_bg1);
13594            assert_eq!(out[3].color, text_color);
13595            assert_eq!(out[4].color, adjusted_bg2);
13596
13597            // for text: "opqrs"
13598            let runs = vec![
13599                generate_test_run(1, text_color), // o
13600                generate_test_run(4, text_color), // pqrs
13601            ];
13602            let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 14); // 6 + 3 + 2 + 3 from first two runs
13603            // new splits "o", "p", "qr", "s"
13604            assert_eq!(
13605                out.iter().map(|r| r.len).collect::<Vec<_>>(),
13606                vec![1, 1, 2, 1]
13607            );
13608            assert_eq!(out[0].color, adjusted_bg2);
13609            assert_eq!(out[1].color, adjusted_bg2);
13610            assert_eq!(out[2].color, text_color);
13611            assert_eq!(out[3].color, adjusted_bg1);
13612        }
13613    }
13614
13615    #[test]
13616    fn test_spacer_pattern_period() {
13617        // line height is smaller than target height, so we just return half the line height
13618        assert_eq!(EditorElement::spacer_pattern_period(10.0, 20.0), 5.0);
13619
13620        // line height is exactly half the target height, perfect match
13621        assert_eq!(EditorElement::spacer_pattern_period(20.0, 10.0), 10.0);
13622
13623        // line height is close to half the target height
13624        assert_eq!(EditorElement::spacer_pattern_period(20.0, 9.0), 10.0);
13625
13626        // line height is close to 1/4 the target height
13627        assert_eq!(EditorElement::spacer_pattern_period(20.0, 4.8), 5.0);
13628    }
13629
13630    #[gpui::test(iterations = 100)]
13631    fn test_random_spacer_pattern_period(mut rng: StdRng) {
13632        let line_height = rng.next_u32() as f32;
13633        let target_height = rng.next_u32() as f32;
13634
13635        let result = EditorElement::spacer_pattern_period(line_height, target_height);
13636
13637        let k = line_height / result;
13638        assert!(k - k.round() < 0.0000001); // approximately integer
13639        assert!((k.round() as u32).is_multiple_of(2));
13640    }
13641
13642    #[test]
13643    fn test_calculate_wrap_width() {
13644        let editor_width = px(800.0);
13645        let em_width = px(8.0);
13646
13647        assert_eq!(
13648            calculate_wrap_width(SoftWrap::GitDiff, editor_width, em_width),
13649            None,
13650        );
13651
13652        assert_eq!(
13653            calculate_wrap_width(SoftWrap::None, editor_width, em_width),
13654            Some(px((MAX_LINE_LEN as f32 / 2.0 * 8.0).ceil())),
13655        );
13656
13657        assert_eq!(
13658            calculate_wrap_width(SoftWrap::EditorWidth, editor_width, em_width),
13659            Some(px(800.0)),
13660        );
13661
13662        assert_eq!(
13663            calculate_wrap_width(SoftWrap::Bounded(72), editor_width, em_width),
13664            Some(px((72.0 * 8.0_f32).ceil())),
13665        );
13666        assert_eq!(
13667            calculate_wrap_width(SoftWrap::Bounded(200), px(400.0), em_width),
13668            Some(px(400.0)),
13669        );
13670    }
13671}