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