Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:33:40.371Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches Β· 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

element.rs

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