1use crate::{
2 ActiveDiagnostic, BlockId, CURSORS_VISIBLE_FOR, ChunkRendererContext, ChunkReplacement,
3 CodeActionSource, ConflictsOurs, ConflictsOursMarker, ConflictsOuter, ConflictsTheirs,
4 ConflictsTheirsMarker, ContextMenuPlacement, CursorShape, CustomBlockId, DisplayDiffHunk,
5 DisplayPoint, DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode,
6 Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, FILE_HEADER_HEIGHT,
7 FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
8 InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineHighlight, LineUp,
9 MAX_LINE_LEN, MINIMAP_FONT_SIZE, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts, PageDown,
10 PageUp, PhantomBreakpointIndicator, Point, RowExt, RowRangeExt, SelectPhase,
11 SelectedTextHighlight, Selection, SelectionDragState, SoftWrap, StickyHeaderExcerpt, ToPoint,
12 ToggleFold,
13 code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
14 display_map::{
15 Block, BlockContext, BlockStyle, DisplaySnapshot, EditorMargins, FoldId, HighlightedChunk,
16 ToDisplayPoint,
17 },
18 editor_settings::{
19 CurrentLineHighlight, DoubleClickInMultibuffer, MinimapThumb, MinimapThumbBorder,
20 ScrollBeyondLastLine, ScrollbarAxes, ScrollbarDiagnostics, ShowMinimap, ShowScrollbar,
21 },
22 git::blame::{BlameRenderer, GitBlame, GlobalBlameRenderer},
23 hover_popover::{
24 self, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
25 POPOVER_RIGHT_OFFSET, hover_at,
26 },
27 inlay_hint_settings,
28 items::BufferSearchHighlights,
29 mouse_context_menu::{self, MenuPosition},
30 scroll::{ActiveScrollbarState, ScrollbarThumbState, scroll_amount::ScrollAmount},
31};
32use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
33use collections::{BTreeMap, HashMap};
34use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
35use file_icons::FileIcons;
36use git::{
37 Oid,
38 blame::{BlameEntry, ParsedCommitMessage},
39 status::FileStatus,
40};
41use gpui::{
42 Action, Along, AnyElement, App, AppContext, AvailableSpace, Axis as ScrollbarAxis, BorderStyle,
43 Bounds, ClickEvent, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase, Edges,
44 Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox,
45 HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero, Keystroke, Length,
46 ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
47 ParentElement, Pixels, ScrollDelta, ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString,
48 Size, StatefulInteractiveElement, Style, Styled, TextRun, TextStyleRefinement, WeakEntity,
49 Window, anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px,
50 quad, relative, size, solid_background, transparent_black,
51};
52use itertools::Itertools;
53use language::language_settings::{
54 IndentGuideBackgroundColoring, IndentGuideColoring, IndentGuideSettings, ShowWhitespaceSetting,
55};
56use markdown::Markdown;
57use multi_buffer::{
58 Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint,
59 MultiBufferRow, RowInfo,
60};
61
62use project::{
63 ProjectPath,
64 debugger::breakpoint_store::{Breakpoint, BreakpointSessionState},
65 project_settings::{GitGutterSetting, GitHunkStyleSetting, ProjectSettings},
66};
67use settings::Settings;
68use smallvec::{SmallVec, smallvec};
69use std::{
70 any::TypeId,
71 borrow::Cow,
72 cmp::{self, Ordering},
73 fmt::{self, Write},
74 iter, mem,
75 ops::{Deref, Range},
76 rc::Rc,
77 sync::Arc,
78 time::{Duration, Instant},
79};
80use sum_tree::Bias;
81use text::{BufferId, SelectionGoal};
82use theme::{ActiveTheme, Appearance, BufferLineHeight, PlayerColor};
83use ui::{ButtonLike, KeyBinding, POPOVER_Y_PADDING, Tooltip, h_flex, prelude::*};
84use unicode_segmentation::UnicodeSegmentation;
85use util::post_inc;
86use util::{RangeExt, ResultExt, debug_panic};
87use workspace::{CollaboratorId, Workspace, item::Item, notifications::NotifyTaskExt};
88
89const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
90const SELECTION_DRAG_DELAY: Duration = Duration::from_millis(300);
91
92/// Determines what kinds of highlights should be applied to a lines background.
93#[derive(Clone, Copy, Default)]
94struct LineHighlightSpec {
95 selection: bool,
96 breakpoint: bool,
97 _active_stack_frame: bool,
98}
99
100#[derive(Debug)]
101struct SelectionLayout {
102 head: DisplayPoint,
103 cursor_shape: CursorShape,
104 is_newest: bool,
105 is_local: bool,
106 range: Range<DisplayPoint>,
107 active_rows: Range<DisplayRow>,
108 user_name: Option<SharedString>,
109}
110
111struct InlineBlameLayout {
112 element: AnyElement,
113 bounds: Bounds<Pixels>,
114 entry: BlameEntry,
115}
116
117impl SelectionLayout {
118 fn new<T: ToPoint + ToDisplayPoint + Clone>(
119 selection: Selection<T>,
120 line_mode: bool,
121 cursor_shape: CursorShape,
122 map: &DisplaySnapshot,
123 is_newest: bool,
124 is_local: bool,
125 user_name: Option<SharedString>,
126 ) -> Self {
127 let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
128 let display_selection = point_selection.map(|p| p.to_display_point(map));
129 let mut range = display_selection.range();
130 let mut head = display_selection.head();
131 let mut active_rows = map.prev_line_boundary(point_selection.start).1.row()
132 ..map.next_line_boundary(point_selection.end).1.row();
133
134 // vim visual line mode
135 if line_mode {
136 let point_range = map.expand_to_line(point_selection.range());
137 range = point_range.start.to_display_point(map)..point_range.end.to_display_point(map);
138 }
139
140 // any vim visual mode (including line mode)
141 if (cursor_shape == CursorShape::Block || cursor_shape == CursorShape::Hollow)
142 && !range.is_empty()
143 && !selection.reversed
144 {
145 if head.column() > 0 {
146 head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left)
147 } else if head.row().0 > 0 && head != map.max_point() {
148 head = map.clip_point(
149 DisplayPoint::new(
150 head.row().previous_row(),
151 map.line_len(head.row().previous_row()),
152 ),
153 Bias::Left,
154 );
155 // updating range.end is a no-op unless you're cursor is
156 // on the newline containing a multi-buffer divider
157 // in which case the clip_point may have moved the head up
158 // an additional row.
159 range.end = DisplayPoint::new(head.row().next_row(), 0);
160 active_rows.end = head.row();
161 }
162 }
163
164 Self {
165 head,
166 cursor_shape,
167 is_newest,
168 is_local,
169 range,
170 active_rows,
171 user_name,
172 }
173 }
174}
175
176pub struct EditorElement {
177 editor: Entity<Editor>,
178 style: EditorStyle,
179}
180
181type DisplayRowDelta = u32;
182
183impl EditorElement {
184 pub(crate) const SCROLLBAR_WIDTH: Pixels = px(15.);
185
186 pub fn new(editor: &Entity<Editor>, style: EditorStyle) -> Self {
187 Self {
188 editor: editor.clone(),
189 style,
190 }
191 }
192
193 fn register_actions(&self, window: &mut Window, cx: &mut App) {
194 let editor = &self.editor;
195 editor.update(cx, |editor, cx| {
196 for action in editor.editor_actions.borrow().values() {
197 (action)(editor, window, cx)
198 }
199 });
200
201 crate::rust_analyzer_ext::apply_related_actions(editor, window, cx);
202 crate::clangd_ext::apply_related_actions(editor, window, cx);
203
204 register_action(editor, window, Editor::open_context_menu);
205 register_action(editor, window, Editor::move_left);
206 register_action(editor, window, Editor::move_right);
207 register_action(editor, window, Editor::move_down);
208 register_action(editor, window, Editor::move_down_by_lines);
209 register_action(editor, window, Editor::select_down_by_lines);
210 register_action(editor, window, Editor::move_up);
211 register_action(editor, window, Editor::move_up_by_lines);
212 register_action(editor, window, Editor::select_up_by_lines);
213 register_action(editor, window, Editor::select_page_down);
214 register_action(editor, window, Editor::select_page_up);
215 register_action(editor, window, Editor::cancel);
216 register_action(editor, window, Editor::newline);
217 register_action(editor, window, Editor::newline_above);
218 register_action(editor, window, Editor::newline_below);
219 register_action(editor, window, Editor::backspace);
220 register_action(editor, window, Editor::delete);
221 register_action(editor, window, Editor::tab);
222 register_action(editor, window, Editor::backtab);
223 register_action(editor, window, Editor::indent);
224 register_action(editor, window, Editor::outdent);
225 register_action(editor, window, Editor::autoindent);
226 register_action(editor, window, Editor::delete_line);
227 register_action(editor, window, Editor::join_lines);
228 register_action(editor, window, Editor::sort_lines_case_sensitive);
229 register_action(editor, window, Editor::sort_lines_case_insensitive);
230 register_action(editor, window, Editor::reverse_lines);
231 register_action(editor, window, Editor::shuffle_lines);
232 register_action(editor, window, Editor::toggle_case);
233 register_action(editor, window, Editor::convert_to_upper_case);
234 register_action(editor, window, Editor::convert_to_lower_case);
235 register_action(editor, window, Editor::convert_to_title_case);
236 register_action(editor, window, Editor::convert_to_snake_case);
237 register_action(editor, window, Editor::convert_to_kebab_case);
238 register_action(editor, window, Editor::convert_to_upper_camel_case);
239 register_action(editor, window, Editor::convert_to_lower_camel_case);
240 register_action(editor, window, Editor::convert_to_opposite_case);
241 register_action(editor, window, Editor::convert_to_rot13);
242 register_action(editor, window, Editor::convert_to_rot47);
243 register_action(editor, window, Editor::delete_to_previous_word_start);
244 register_action(editor, window, Editor::delete_to_previous_subword_start);
245 register_action(editor, window, Editor::delete_to_next_word_end);
246 register_action(editor, window, Editor::delete_to_next_subword_end);
247 register_action(editor, window, Editor::delete_to_beginning_of_line);
248 register_action(editor, window, Editor::delete_to_end_of_line);
249 register_action(editor, window, Editor::cut_to_end_of_line);
250 register_action(editor, window, Editor::duplicate_line_up);
251 register_action(editor, window, Editor::duplicate_line_down);
252 register_action(editor, window, Editor::duplicate_selection);
253 register_action(editor, window, Editor::move_line_up);
254 register_action(editor, window, Editor::move_line_down);
255 register_action(editor, window, Editor::transpose);
256 register_action(editor, window, Editor::rewrap);
257 register_action(editor, window, Editor::cut);
258 register_action(editor, window, Editor::kill_ring_cut);
259 register_action(editor, window, Editor::kill_ring_yank);
260 register_action(editor, window, Editor::copy);
261 register_action(editor, window, Editor::copy_and_trim);
262 register_action(editor, window, Editor::paste);
263 register_action(editor, window, Editor::undo);
264 register_action(editor, window, Editor::redo);
265 register_action(editor, window, Editor::move_page_up);
266 register_action(editor, window, Editor::move_page_down);
267 register_action(editor, window, Editor::next_screen);
268 register_action(editor, window, Editor::scroll_cursor_top);
269 register_action(editor, window, Editor::scroll_cursor_center);
270 register_action(editor, window, Editor::scroll_cursor_bottom);
271 register_action(editor, window, Editor::scroll_cursor_center_top_bottom);
272 register_action(editor, window, |editor, _: &LineDown, window, cx| {
273 editor.scroll_screen(&ScrollAmount::Line(1.), window, cx)
274 });
275 register_action(editor, window, |editor, _: &LineUp, window, cx| {
276 editor.scroll_screen(&ScrollAmount::Line(-1.), window, cx)
277 });
278 register_action(editor, window, |editor, _: &HalfPageDown, window, cx| {
279 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx)
280 });
281 register_action(
282 editor,
283 window,
284 |editor, HandleInput(text): &HandleInput, window, cx| {
285 if text.is_empty() {
286 return;
287 }
288 editor.handle_input(text, window, cx);
289 },
290 );
291 register_action(editor, window, |editor, _: &HalfPageUp, window, cx| {
292 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx)
293 });
294 register_action(editor, window, |editor, _: &PageDown, window, cx| {
295 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx)
296 });
297 register_action(editor, window, |editor, _: &PageUp, window, cx| {
298 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx)
299 });
300 register_action(editor, window, Editor::move_to_previous_word_start);
301 register_action(editor, window, Editor::move_to_previous_subword_start);
302 register_action(editor, window, Editor::move_to_next_word_end);
303 register_action(editor, window, Editor::move_to_next_subword_end);
304 register_action(editor, window, Editor::move_to_beginning_of_line);
305 register_action(editor, window, Editor::move_to_end_of_line);
306 register_action(editor, window, Editor::move_to_start_of_paragraph);
307 register_action(editor, window, Editor::move_to_end_of_paragraph);
308 register_action(editor, window, Editor::move_to_beginning);
309 register_action(editor, window, Editor::move_to_end);
310 register_action(editor, window, Editor::move_to_start_of_excerpt);
311 register_action(editor, window, Editor::move_to_start_of_next_excerpt);
312 register_action(editor, window, Editor::move_to_end_of_excerpt);
313 register_action(editor, window, Editor::move_to_end_of_previous_excerpt);
314 register_action(editor, window, Editor::select_up);
315 register_action(editor, window, Editor::select_down);
316 register_action(editor, window, Editor::select_left);
317 register_action(editor, window, Editor::select_right);
318 register_action(editor, window, Editor::select_to_previous_word_start);
319 register_action(editor, window, Editor::select_to_previous_subword_start);
320 register_action(editor, window, Editor::select_to_next_word_end);
321 register_action(editor, window, Editor::select_to_next_subword_end);
322 register_action(editor, window, Editor::select_to_beginning_of_line);
323 register_action(editor, window, Editor::select_to_end_of_line);
324 register_action(editor, window, Editor::select_to_start_of_paragraph);
325 register_action(editor, window, Editor::select_to_end_of_paragraph);
326 register_action(editor, window, Editor::select_to_start_of_excerpt);
327 register_action(editor, window, Editor::select_to_start_of_next_excerpt);
328 register_action(editor, window, Editor::select_to_end_of_excerpt);
329 register_action(editor, window, Editor::select_to_end_of_previous_excerpt);
330 register_action(editor, window, Editor::select_to_beginning);
331 register_action(editor, window, Editor::select_to_end);
332 register_action(editor, window, Editor::select_all);
333 register_action(editor, window, |editor, action, window, cx| {
334 editor.select_all_matches(action, window, cx).log_err();
335 });
336 register_action(editor, window, Editor::select_line);
337 register_action(editor, window, Editor::split_selection_into_lines);
338 register_action(editor, window, Editor::add_selection_above);
339 register_action(editor, window, Editor::add_selection_below);
340 register_action(editor, window, |editor, action, window, cx| {
341 editor.select_next(action, window, cx).log_err();
342 });
343 register_action(editor, window, |editor, action, window, cx| {
344 editor.select_previous(action, window, cx).log_err();
345 });
346 register_action(editor, window, |editor, action, window, cx| {
347 editor.find_next_match(action, window, cx).log_err();
348 });
349 register_action(editor, window, |editor, action, window, cx| {
350 editor.find_previous_match(action, window, cx).log_err();
351 });
352 register_action(editor, window, Editor::toggle_comments);
353 register_action(editor, window, Editor::select_larger_syntax_node);
354 register_action(editor, window, Editor::select_smaller_syntax_node);
355 register_action(editor, window, Editor::select_enclosing_symbol);
356 register_action(editor, window, Editor::move_to_enclosing_bracket);
357 register_action(editor, window, Editor::undo_selection);
358 register_action(editor, window, Editor::redo_selection);
359 if !editor.read(cx).is_singleton(cx) {
360 register_action(editor, window, Editor::expand_excerpts);
361 register_action(editor, window, Editor::expand_excerpts_up);
362 register_action(editor, window, Editor::expand_excerpts_down);
363 }
364 register_action(editor, window, Editor::go_to_diagnostic);
365 register_action(editor, window, Editor::go_to_prev_diagnostic);
366 register_action(editor, window, Editor::go_to_next_hunk);
367 register_action(editor, window, Editor::go_to_prev_hunk);
368 register_action(editor, window, |editor, action, window, cx| {
369 editor
370 .go_to_definition(action, window, cx)
371 .detach_and_log_err(cx);
372 });
373 register_action(editor, window, |editor, action, window, cx| {
374 editor
375 .go_to_definition_split(action, window, cx)
376 .detach_and_log_err(cx);
377 });
378 register_action(editor, window, |editor, action, window, cx| {
379 editor
380 .go_to_declaration(action, window, cx)
381 .detach_and_log_err(cx);
382 });
383 register_action(editor, window, |editor, action, window, cx| {
384 editor
385 .go_to_declaration_split(action, window, cx)
386 .detach_and_log_err(cx);
387 });
388 register_action(editor, window, |editor, action, window, cx| {
389 editor
390 .go_to_implementation(action, window, cx)
391 .detach_and_log_err(cx);
392 });
393 register_action(editor, window, |editor, action, window, cx| {
394 editor
395 .go_to_implementation_split(action, window, cx)
396 .detach_and_log_err(cx);
397 });
398 register_action(editor, window, |editor, action, window, cx| {
399 editor
400 .go_to_type_definition(action, window, cx)
401 .detach_and_log_err(cx);
402 });
403 register_action(editor, window, |editor, action, window, cx| {
404 editor
405 .go_to_type_definition_split(action, window, cx)
406 .detach_and_log_err(cx);
407 });
408 register_action(editor, window, Editor::open_url);
409 register_action(editor, window, Editor::open_selected_filename);
410 register_action(editor, window, Editor::fold);
411 register_action(editor, window, Editor::fold_at_level);
412 register_action(editor, window, Editor::fold_all);
413 register_action(editor, window, Editor::fold_function_bodies);
414 register_action(editor, window, Editor::fold_recursive);
415 register_action(editor, window, Editor::toggle_fold);
416 register_action(editor, window, Editor::toggle_fold_recursive);
417 register_action(editor, window, Editor::unfold_lines);
418 register_action(editor, window, Editor::unfold_recursive);
419 register_action(editor, window, Editor::unfold_all);
420 register_action(editor, window, Editor::fold_selected_ranges);
421 register_action(editor, window, Editor::set_mark);
422 register_action(editor, window, Editor::swap_selection_ends);
423 register_action(editor, window, Editor::show_completions);
424 register_action(editor, window, Editor::show_word_completions);
425 register_action(editor, window, Editor::toggle_code_actions);
426 register_action(editor, window, Editor::open_excerpts);
427 register_action(editor, window, Editor::open_excerpts_in_split);
428 register_action(editor, window, Editor::open_proposed_changes_editor);
429 register_action(editor, window, Editor::toggle_soft_wrap);
430 register_action(editor, window, Editor::toggle_tab_bar);
431 register_action(editor, window, Editor::toggle_line_numbers);
432 register_action(editor, window, Editor::toggle_relative_line_numbers);
433 register_action(editor, window, Editor::toggle_indent_guides);
434 register_action(editor, window, Editor::toggle_inlay_hints);
435 register_action(editor, window, Editor::toggle_edit_predictions);
436 if editor.read(cx).diagnostics_enabled() {
437 register_action(editor, window, Editor::toggle_diagnostics);
438 }
439 if editor.read(cx).inline_diagnostics_enabled() {
440 register_action(editor, window, Editor::toggle_inline_diagnostics);
441 }
442 if editor.read(cx).supports_minimap(cx) {
443 register_action(editor, window, Editor::toggle_minimap);
444 }
445 register_action(editor, window, hover_popover::hover);
446 register_action(editor, window, Editor::reveal_in_finder);
447 register_action(editor, window, Editor::copy_path);
448 register_action(editor, window, Editor::copy_relative_path);
449 register_action(editor, window, Editor::copy_file_name);
450 register_action(editor, window, Editor::copy_file_name_without_extension);
451 register_action(editor, window, Editor::copy_highlight_json);
452 register_action(editor, window, Editor::copy_permalink_to_line);
453 register_action(editor, window, Editor::open_permalink_to_line);
454 register_action(editor, window, Editor::copy_file_location);
455 register_action(editor, window, Editor::toggle_git_blame);
456 register_action(editor, window, Editor::toggle_git_blame_inline);
457 register_action(editor, window, Editor::open_git_blame_commit);
458 register_action(editor, window, Editor::toggle_selected_diff_hunks);
459 register_action(editor, window, Editor::toggle_staged_selected_diff_hunks);
460 register_action(editor, window, Editor::stage_and_next);
461 register_action(editor, window, Editor::unstage_and_next);
462 register_action(editor, window, Editor::expand_all_diff_hunks);
463 register_action(editor, window, Editor::go_to_previous_change);
464 register_action(editor, window, Editor::go_to_next_change);
465
466 register_action(editor, window, |editor, action, window, cx| {
467 if let Some(task) = editor.format(action, window, cx) {
468 task.detach_and_notify_err(window, cx);
469 } else {
470 cx.propagate();
471 }
472 });
473 register_action(editor, window, |editor, action, window, cx| {
474 if let Some(task) = editor.format_selections(action, window, cx) {
475 task.detach_and_notify_err(window, cx);
476 } else {
477 cx.propagate();
478 }
479 });
480 register_action(editor, window, |editor, action, window, cx| {
481 if let Some(task) = editor.organize_imports(action, window, cx) {
482 task.detach_and_notify_err(window, cx);
483 } else {
484 cx.propagate();
485 }
486 });
487 register_action(editor, window, Editor::restart_language_server);
488 register_action(editor, window, Editor::stop_language_server);
489 register_action(editor, window, Editor::show_character_palette);
490 register_action(editor, window, |editor, action, window, cx| {
491 if let Some(task) = editor.confirm_completion(action, window, cx) {
492 task.detach_and_notify_err(window, cx);
493 } else {
494 cx.propagate();
495 }
496 });
497 register_action(editor, window, |editor, action, window, cx| {
498 if let Some(task) = editor.confirm_completion_replace(action, window, cx) {
499 task.detach_and_notify_err(window, cx);
500 } else {
501 cx.propagate();
502 }
503 });
504 register_action(editor, window, |editor, action, window, cx| {
505 if let Some(task) = editor.confirm_completion_insert(action, window, cx) {
506 task.detach_and_notify_err(window, cx);
507 } else {
508 cx.propagate();
509 }
510 });
511 register_action(editor, window, |editor, action, window, cx| {
512 if let Some(task) = editor.compose_completion(action, window, cx) {
513 task.detach_and_notify_err(window, cx);
514 } else {
515 cx.propagate();
516 }
517 });
518 register_action(editor, window, |editor, action, window, cx| {
519 if let Some(task) = editor.confirm_code_action(action, window, cx) {
520 task.detach_and_notify_err(window, cx);
521 } else {
522 cx.propagate();
523 }
524 });
525 register_action(editor, window, |editor, action, window, cx| {
526 if let Some(task) = editor.rename(action, window, cx) {
527 task.detach_and_notify_err(window, cx);
528 } else {
529 cx.propagate();
530 }
531 });
532 register_action(editor, window, |editor, action, window, cx| {
533 if let Some(task) = editor.confirm_rename(action, window, cx) {
534 task.detach_and_notify_err(window, cx);
535 } else {
536 cx.propagate();
537 }
538 });
539 register_action(editor, window, |editor, action, window, cx| {
540 if let Some(task) = editor.find_all_references(action, window, cx) {
541 task.detach_and_log_err(cx);
542 } else {
543 cx.propagate();
544 }
545 });
546 register_action(editor, window, Editor::show_signature_help);
547 register_action(editor, window, Editor::next_edit_prediction);
548 register_action(editor, window, Editor::previous_edit_prediction);
549 register_action(editor, window, Editor::show_inline_completion);
550 register_action(editor, window, Editor::context_menu_first);
551 register_action(editor, window, Editor::context_menu_prev);
552 register_action(editor, window, Editor::context_menu_next);
553 register_action(editor, window, Editor::context_menu_last);
554 register_action(editor, window, Editor::display_cursor_names);
555 register_action(editor, window, Editor::unique_lines_case_insensitive);
556 register_action(editor, window, Editor::unique_lines_case_sensitive);
557 register_action(editor, window, Editor::accept_partial_inline_completion);
558 register_action(editor, window, Editor::accept_edit_prediction);
559 register_action(editor, window, Editor::restore_file);
560 register_action(editor, window, Editor::git_restore);
561 register_action(editor, window, Editor::apply_all_diff_hunks);
562 register_action(editor, window, Editor::apply_selected_diff_hunks);
563 register_action(editor, window, Editor::open_active_item_in_terminal);
564 register_action(editor, window, Editor::reload_file);
565 register_action(editor, window, Editor::spawn_nearest_task);
566 register_action(editor, window, Editor::insert_uuid_v4);
567 register_action(editor, window, Editor::insert_uuid_v7);
568 register_action(editor, window, Editor::open_selections_in_multibuffer);
569 if cx.has_flag::<DebuggerFeatureFlag>() {
570 register_action(editor, window, Editor::toggle_breakpoint);
571 register_action(editor, window, Editor::edit_log_breakpoint);
572 register_action(editor, window, Editor::enable_breakpoint);
573 register_action(editor, window, Editor::disable_breakpoint);
574 }
575 }
576
577 fn register_key_listeners(&self, window: &mut Window, _: &mut App, layout: &EditorLayout) {
578 let position_map = layout.position_map.clone();
579 window.on_key_event({
580 let editor = self.editor.clone();
581 move |event: &ModifiersChangedEvent, phase, window, cx| {
582 if phase != DispatchPhase::Bubble {
583 return;
584 }
585 editor.update(cx, |editor, cx| {
586 let inlay_hint_settings = inlay_hint_settings(
587 editor.selections.newest_anchor().head(),
588 &editor.buffer.read(cx).snapshot(cx),
589 cx,
590 );
591
592 if let Some(inlay_modifiers) = inlay_hint_settings
593 .toggle_on_modifiers_press
594 .as_ref()
595 .filter(|modifiers| modifiers.modified())
596 {
597 editor.refresh_inlay_hints(
598 InlayHintRefreshReason::ModifiersChanged(
599 inlay_modifiers == &event.modifiers,
600 ),
601 cx,
602 );
603 }
604
605 if editor.hover_state.focused(window, cx) {
606 return;
607 }
608
609 editor.handle_modifiers_changed(event.modifiers, &position_map, window, cx);
610 })
611 }
612 });
613 }
614
615 fn mouse_left_down(
616 editor: &mut Editor,
617 event: &MouseDownEvent,
618 hovered_hunk: Option<Range<Anchor>>,
619 position_map: &PositionMap,
620 line_numbers: &HashMap<MultiBufferRow, LineNumberLayout>,
621 window: &mut Window,
622 cx: &mut Context<Editor>,
623 ) {
624 if window.default_prevented() {
625 return;
626 }
627
628 let text_hitbox = &position_map.text_hitbox;
629 let gutter_hitbox = &position_map.gutter_hitbox;
630 let point_for_position = position_map.point_for_position(event.position);
631 let mut click_count = event.click_count;
632 let mut modifiers = event.modifiers;
633
634 if let Some(hovered_hunk) = hovered_hunk {
635 editor.toggle_single_diff_hunk(hovered_hunk, cx);
636 cx.notify();
637 return;
638 } else if gutter_hitbox.is_hovered(window) {
639 click_count = 3; // Simulate triple-click when clicking the gutter to select lines
640 } else if !text_hitbox.is_hovered(window) {
641 return;
642 }
643
644 if editor.drag_and_drop_selection_enabled && click_count == 1 {
645 let newest_anchor = editor.selections.newest_anchor();
646 let snapshot = editor.snapshot(window, cx);
647 let selection = newest_anchor.map(|anchor| anchor.to_display_point(&snapshot));
648 if point_for_position.intersects_selection(&selection) {
649 editor.selection_drag_state = SelectionDragState::ReadyToDrag {
650 selection: newest_anchor.clone(),
651 click_position: event.position,
652 mouse_down_time: Instant::now(),
653 };
654 cx.stop_propagation();
655 return;
656 }
657 }
658
659 let is_singleton = editor.buffer().read(cx).is_singleton();
660
661 if click_count == 2 && !is_singleton {
662 match EditorSettings::get_global(cx).double_click_in_multibuffer {
663 DoubleClickInMultibuffer::Select => {
664 // do nothing special on double click, all selection logic is below
665 }
666 DoubleClickInMultibuffer::Open => {
667 if modifiers.alt {
668 // if double click is made with alt, pretend it's a regular double click without opening and alt,
669 // and run the selection logic.
670 modifiers.alt = false;
671 } else {
672 let scroll_position_row =
673 position_map.scroll_pixel_position.y / position_map.line_height;
674 let display_row = (((event.position - gutter_hitbox.bounds.origin).y
675 + position_map.scroll_pixel_position.y)
676 / position_map.line_height)
677 as u32;
678 let multi_buffer_row = position_map
679 .snapshot
680 .display_point_to_point(
681 DisplayPoint::new(DisplayRow(display_row), 0),
682 Bias::Right,
683 )
684 .row;
685 let line_offset_from_top = display_row - scroll_position_row as u32;
686 // if double click is made without alt, open the corresponding excerp
687 editor.open_excerpts_common(
688 Some(JumpData::MultiBufferRow {
689 row: MultiBufferRow(multi_buffer_row),
690 line_offset_from_top,
691 }),
692 false,
693 window,
694 cx,
695 );
696 return;
697 }
698 }
699 }
700 }
701
702 let position = point_for_position.previous_valid;
703 let multi_cursor_modifier = Editor::multi_cursor_modifier(true, &modifiers, cx);
704 if Editor::columnar_selection_modifiers(multi_cursor_modifier, &modifiers) {
705 editor.select(
706 SelectPhase::BeginColumnar {
707 position,
708 reset: true,
709 goal_column: point_for_position.exact_unclipped.column(),
710 },
711 window,
712 cx,
713 );
714 } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.secondary()
715 {
716 editor.select(
717 SelectPhase::Extend {
718 position,
719 click_count,
720 },
721 window,
722 cx,
723 );
724 } else {
725 editor.select(
726 SelectPhase::Begin {
727 position,
728 add: multi_cursor_modifier,
729 click_count,
730 },
731 window,
732 cx,
733 );
734 }
735 cx.stop_propagation();
736
737 if !is_singleton {
738 let display_row = (((event.position - gutter_hitbox.bounds.origin).y
739 + position_map.scroll_pixel_position.y)
740 / position_map.line_height) as u32;
741 let multi_buffer_row = position_map
742 .snapshot
743 .display_point_to_point(DisplayPoint::new(DisplayRow(display_row), 0), Bias::Right)
744 .row;
745 if line_numbers
746 .get(&MultiBufferRow(multi_buffer_row))
747 .and_then(|line_number| line_number.hitbox.as_ref())
748 .is_some_and(|hitbox| hitbox.contains(&event.position))
749 {
750 let scroll_position_row =
751 position_map.scroll_pixel_position.y / position_map.line_height;
752 let line_offset_from_top = display_row - scroll_position_row as u32;
753
754 editor.open_excerpts_common(
755 Some(JumpData::MultiBufferRow {
756 row: MultiBufferRow(multi_buffer_row),
757 line_offset_from_top,
758 }),
759 modifiers.alt,
760 window,
761 cx,
762 );
763 cx.stop_propagation();
764 }
765 }
766 }
767
768 fn mouse_right_down(
769 editor: &mut Editor,
770 event: &MouseDownEvent,
771 position_map: &PositionMap,
772 window: &mut Window,
773 cx: &mut Context<Editor>,
774 ) {
775 if position_map.gutter_hitbox.is_hovered(window) {
776 let gutter_right_padding = editor.gutter_dimensions.right_padding;
777 let hitbox = &position_map.gutter_hitbox;
778
779 if event.position.x <= hitbox.bounds.right() - gutter_right_padding {
780 let point_for_position = position_map.point_for_position(event.position);
781 editor.set_breakpoint_context_menu(
782 point_for_position.previous_valid.row(),
783 None,
784 event.position,
785 window,
786 cx,
787 );
788 }
789 return;
790 }
791
792 if !position_map.text_hitbox.is_hovered(window) {
793 return;
794 }
795
796 let point_for_position = position_map.point_for_position(event.position);
797 mouse_context_menu::deploy_context_menu(
798 editor,
799 Some(event.position),
800 point_for_position.previous_valid,
801 window,
802 cx,
803 );
804 cx.stop_propagation();
805 }
806
807 fn mouse_middle_down(
808 editor: &mut Editor,
809 event: &MouseDownEvent,
810 position_map: &PositionMap,
811 window: &mut Window,
812 cx: &mut Context<Editor>,
813 ) {
814 if !position_map.text_hitbox.is_hovered(window) || window.default_prevented() {
815 return;
816 }
817
818 let point_for_position = position_map.point_for_position(event.position);
819 let position = point_for_position.previous_valid;
820
821 editor.select(
822 SelectPhase::BeginColumnar {
823 position,
824 reset: true,
825 goal_column: point_for_position.exact_unclipped.column(),
826 },
827 window,
828 cx,
829 );
830 }
831
832 fn mouse_up(
833 editor: &mut Editor,
834 event: &MouseUpEvent,
835 position_map: &PositionMap,
836 window: &mut Window,
837 cx: &mut Context<Editor>,
838 ) {
839 let text_hitbox = &position_map.text_hitbox;
840 let end_selection = editor.has_pending_selection();
841 let pending_nonempty_selections = editor.has_pending_nonempty_selection();
842 let point_for_position = position_map.point_for_position(event.position);
843
844 match editor.selection_drag_state {
845 SelectionDragState::ReadyToDrag {
846 selection: _,
847 ref click_position,
848 mouse_down_time: _,
849 } => {
850 if event.position == *click_position {
851 editor.select(
852 SelectPhase::Begin {
853 position: point_for_position.previous_valid,
854 add: false,
855 click_count: 1, // ready to drag state only occurs on click count 1
856 },
857 window,
858 cx,
859 );
860 editor.selection_drag_state = SelectionDragState::None;
861 cx.stop_propagation();
862 return;
863 } else {
864 debug_panic!("drag state can never be in ready state after drag")
865 }
866 }
867 SelectionDragState::Dragging { ref selection, .. } => {
868 let snapshot = editor.snapshot(window, cx);
869 let selection_display = selection.map(|anchor| anchor.to_display_point(&snapshot));
870 if !point_for_position.intersects_selection(&selection_display)
871 && text_hitbox.is_hovered(window)
872 {
873 let is_cut = !(cfg!(target_os = "macos") && event.modifiers.alt
874 || cfg!(not(target_os = "macos")) && event.modifiers.control);
875 editor.move_selection_on_drop(
876 &selection.clone(),
877 point_for_position.previous_valid,
878 is_cut,
879 window,
880 cx,
881 );
882 }
883 editor.selection_drag_state = SelectionDragState::None;
884 cx.stop_propagation();
885 cx.notify();
886 return;
887 }
888 _ => {}
889 }
890
891 if end_selection {
892 editor.select(SelectPhase::End, window, cx);
893 }
894
895 if end_selection && pending_nonempty_selections {
896 cx.stop_propagation();
897 } else if cfg!(any(target_os = "linux", target_os = "freebsd"))
898 && event.button == MouseButton::Middle
899 {
900 if !text_hitbox.is_hovered(window) || editor.read_only(cx) {
901 return;
902 }
903
904 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
905 if EditorSettings::get_global(cx).middle_click_paste {
906 if let Some(text) = cx.read_from_primary().and_then(|item| item.text()) {
907 let point_for_position = position_map.point_for_position(event.position);
908 let position = point_for_position.previous_valid;
909
910 editor.select(
911 SelectPhase::Begin {
912 position,
913 add: false,
914 click_count: 1,
915 },
916 window,
917 cx,
918 );
919 editor.insert(&text, window, cx);
920 }
921 cx.stop_propagation()
922 }
923 }
924 }
925
926 fn click(
927 editor: &mut Editor,
928 event: &ClickEvent,
929 position_map: &PositionMap,
930 window: &mut Window,
931 cx: &mut Context<Editor>,
932 ) {
933 let text_hitbox = &position_map.text_hitbox;
934 let pending_nonempty_selections = editor.has_pending_nonempty_selection();
935
936 let hovered_link_modifier = Editor::multi_cursor_modifier(false, &event.modifiers(), cx);
937
938 if !pending_nonempty_selections && hovered_link_modifier && text_hitbox.is_hovered(window) {
939 let point = position_map.point_for_position(event.up.position);
940 editor.handle_click_hovered_link(point, event.modifiers(), window, cx);
941
942 cx.stop_propagation();
943 }
944 }
945
946 fn mouse_dragged(
947 editor: &mut Editor,
948 event: &MouseMoveEvent,
949 position_map: &PositionMap,
950 window: &mut Window,
951 cx: &mut Context<Editor>,
952 ) {
953 if !editor.has_pending_selection()
954 && matches!(editor.selection_drag_state, SelectionDragState::None)
955 {
956 return;
957 }
958
959 let point_for_position = position_map.point_for_position(event.position);
960 let text_hitbox = &position_map.text_hitbox;
961
962 let scroll_delta = {
963 let text_bounds = text_hitbox.bounds;
964 let mut scroll_delta = gpui::Point::<f32>::default();
965 let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
966 let top = text_bounds.origin.y + vertical_margin;
967 let bottom = text_bounds.bottom_left().y - vertical_margin;
968 if event.position.y < top {
969 scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y);
970 }
971 if event.position.y > bottom {
972 scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
973 }
974
975 // We need horizontal width of text
976 let style = editor.style.clone().unwrap_or_default();
977 let font_id = window.text_system().resolve_font(&style.text.font());
978 let font_size = style.text.font_size.to_pixels(window.rem_size());
979 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
980
981 let scroll_margin_x = EditorSettings::get_global(cx).horizontal_scroll_margin;
982
983 let scroll_space: Pixels = scroll_margin_x * em_width;
984
985 let left = text_bounds.origin.x + scroll_space;
986 let right = text_bounds.top_right().x - scroll_space;
987
988 if event.position.x < left {
989 scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
990 }
991 if event.position.x > right {
992 scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
993 }
994 scroll_delta
995 };
996
997 if !editor.has_pending_selection() {
998 let drop_anchor = position_map
999 .snapshot
1000 .display_point_to_anchor(point_for_position.previous_valid, Bias::Left);
1001 match editor.selection_drag_state {
1002 SelectionDragState::Dragging {
1003 ref mut drop_cursor,
1004 ref mut hide_drop_cursor,
1005 ..
1006 } => {
1007 drop_cursor.start = drop_anchor;
1008 drop_cursor.end = drop_anchor;
1009 *hide_drop_cursor = !text_hitbox.is_hovered(window);
1010 editor.apply_scroll_delta(scroll_delta, window, cx);
1011 cx.notify();
1012 }
1013 SelectionDragState::ReadyToDrag {
1014 ref selection,
1015 ref click_position,
1016 ref mouse_down_time,
1017 } => {
1018 if mouse_down_time.elapsed() >= SELECTION_DRAG_DELAY {
1019 let drop_cursor = Selection {
1020 id: post_inc(&mut editor.selections.next_selection_id),
1021 start: drop_anchor,
1022 end: drop_anchor,
1023 reversed: false,
1024 goal: SelectionGoal::None,
1025 };
1026 editor.selection_drag_state = SelectionDragState::Dragging {
1027 selection: selection.clone(),
1028 drop_cursor,
1029 hide_drop_cursor: false,
1030 };
1031 editor.apply_scroll_delta(scroll_delta, window, cx);
1032 cx.notify();
1033 } else {
1034 let click_point = position_map.point_for_position(*click_position);
1035 editor.selection_drag_state = SelectionDragState::None;
1036 editor.select(
1037 SelectPhase::Begin {
1038 position: click_point.previous_valid,
1039 add: false,
1040 click_count: 1,
1041 },
1042 window,
1043 cx,
1044 );
1045 editor.select(
1046 SelectPhase::Update {
1047 position: point_for_position.previous_valid,
1048 goal_column: point_for_position.exact_unclipped.column(),
1049 scroll_delta,
1050 },
1051 window,
1052 cx,
1053 );
1054 }
1055 }
1056 _ => {}
1057 }
1058 } else {
1059 editor.select(
1060 SelectPhase::Update {
1061 position: point_for_position.previous_valid,
1062 goal_column: point_for_position.exact_unclipped.column(),
1063 scroll_delta,
1064 },
1065 window,
1066 cx,
1067 );
1068 }
1069 }
1070
1071 fn mouse_moved(
1072 editor: &mut Editor,
1073 event: &MouseMoveEvent,
1074 position_map: &PositionMap,
1075 window: &mut Window,
1076 cx: &mut Context<Editor>,
1077 ) {
1078 let text_hitbox = &position_map.text_hitbox;
1079 let gutter_hitbox = &position_map.gutter_hitbox;
1080 let modifiers = event.modifiers;
1081 let text_hovered = text_hitbox.is_hovered(window);
1082 let gutter_hovered = gutter_hitbox.is_hovered(window);
1083 editor.set_gutter_hovered(gutter_hovered, cx);
1084 editor.mouse_cursor_hidden = false;
1085
1086 let point_for_position = position_map.point_for_position(event.position);
1087 let valid_point = point_for_position.previous_valid;
1088
1089 let hovered_diff_control = position_map
1090 .diff_hunk_control_bounds
1091 .iter()
1092 .find(|(_, bounds)| bounds.contains(&event.position))
1093 .map(|(row, _)| *row);
1094
1095 let hovered_diff_hunk_row = if let Some(control_row) = hovered_diff_control {
1096 Some(control_row)
1097 } else {
1098 if text_hovered {
1099 let current_row = valid_point.row();
1100 position_map.display_hunks.iter().find_map(|(hunk, _)| {
1101 if let DisplayDiffHunk::Unfolded {
1102 display_row_range, ..
1103 } = hunk
1104 {
1105 if display_row_range.contains(¤t_row) {
1106 Some(display_row_range.start)
1107 } else {
1108 None
1109 }
1110 } else {
1111 None
1112 }
1113 })
1114 } else {
1115 None
1116 }
1117 };
1118
1119 if hovered_diff_hunk_row != editor.hovered_diff_hunk_row {
1120 editor.hovered_diff_hunk_row = hovered_diff_hunk_row;
1121 cx.notify();
1122 }
1123
1124 if let Some((bounds, blame_entry)) = &position_map.inline_blame_bounds {
1125 let mouse_over_inline_blame = bounds.contains(&event.position);
1126 let mouse_over_popover = editor
1127 .inline_blame_popover
1128 .as_ref()
1129 .and_then(|state| state.popover_bounds)
1130 .map_or(false, |bounds| bounds.contains(&event.position));
1131
1132 if mouse_over_inline_blame || mouse_over_popover {
1133 editor.show_blame_popover(&blame_entry, event.position, cx);
1134 } else {
1135 editor.hide_blame_popover(cx);
1136 }
1137 } else {
1138 editor.hide_blame_popover(cx);
1139 }
1140
1141 let breakpoint_indicator = if gutter_hovered {
1142 let buffer_anchor = position_map
1143 .snapshot
1144 .display_point_to_anchor(valid_point, Bias::Left);
1145
1146 if let Some((buffer_snapshot, file)) = position_map
1147 .snapshot
1148 .buffer_snapshot
1149 .buffer_for_excerpt(buffer_anchor.excerpt_id)
1150 .and_then(|buffer| buffer.file().map(|file| (buffer, file)))
1151 {
1152 let as_point = text::ToPoint::to_point(&buffer_anchor.text_anchor, buffer_snapshot);
1153
1154 let is_visible = editor
1155 .gutter_breakpoint_indicator
1156 .0
1157 .map_or(false, |indicator| indicator.is_active);
1158
1159 let has_existing_breakpoint =
1160 editor.breakpoint_store.as_ref().map_or(false, |store| {
1161 let Some(project) = &editor.project else {
1162 return false;
1163 };
1164 let Some(abs_path) = project.read(cx).absolute_path(
1165 &ProjectPath {
1166 path: file.path().clone(),
1167 worktree_id: file.worktree_id(cx),
1168 },
1169 cx,
1170 ) else {
1171 return false;
1172 };
1173 store
1174 .read(cx)
1175 .breakpoint_at_row(&abs_path, as_point.row, cx)
1176 .is_some()
1177 });
1178
1179 if !is_visible {
1180 editor.gutter_breakpoint_indicator.1.get_or_insert_with(|| {
1181 cx.spawn(async move |this, cx| {
1182 cx.background_executor()
1183 .timer(Duration::from_millis(200))
1184 .await;
1185
1186 this.update(cx, |this, cx| {
1187 if let Some(indicator) = this.gutter_breakpoint_indicator.0.as_mut()
1188 {
1189 indicator.is_active = true;
1190 cx.notify();
1191 }
1192 })
1193 .ok();
1194 })
1195 });
1196 }
1197
1198 Some(PhantomBreakpointIndicator {
1199 display_row: valid_point.row(),
1200 is_active: is_visible,
1201 collides_with_existing_breakpoint: has_existing_breakpoint,
1202 })
1203 } else {
1204 editor.gutter_breakpoint_indicator.1 = None;
1205 None
1206 }
1207 } else {
1208 editor.gutter_breakpoint_indicator.1 = None;
1209 None
1210 };
1211
1212 if &breakpoint_indicator != &editor.gutter_breakpoint_indicator.0 {
1213 editor.gutter_breakpoint_indicator.0 = breakpoint_indicator;
1214 cx.notify();
1215 }
1216
1217 // Don't trigger hover popover if mouse is hovering over context menu
1218 if text_hovered {
1219 editor.update_hovered_link(
1220 point_for_position,
1221 &position_map.snapshot,
1222 modifiers,
1223 window,
1224 cx,
1225 );
1226
1227 if let Some(point) = point_for_position.as_valid() {
1228 let anchor = position_map
1229 .snapshot
1230 .buffer_snapshot
1231 .anchor_before(point.to_offset(&position_map.snapshot, Bias::Left));
1232 hover_at(editor, Some(anchor), window, cx);
1233 Self::update_visible_cursor(editor, point, position_map, window, cx);
1234 } else {
1235 hover_at(editor, None, window, cx);
1236 }
1237 } else {
1238 editor.hide_hovered_link(cx);
1239 hover_at(editor, None, window, cx);
1240 }
1241 }
1242
1243 fn update_visible_cursor(
1244 editor: &mut Editor,
1245 point: DisplayPoint,
1246 position_map: &PositionMap,
1247 window: &mut Window,
1248 cx: &mut Context<Editor>,
1249 ) {
1250 let snapshot = &position_map.snapshot;
1251 let Some(hub) = editor.collaboration_hub() else {
1252 return;
1253 };
1254 let start = snapshot.display_snapshot.clip_point(
1255 DisplayPoint::new(point.row(), point.column().saturating_sub(1)),
1256 Bias::Left,
1257 );
1258 let end = snapshot.display_snapshot.clip_point(
1259 DisplayPoint::new(
1260 point.row(),
1261 (point.column() + 1).min(snapshot.line_len(point.row())),
1262 ),
1263 Bias::Right,
1264 );
1265
1266 let range = snapshot
1267 .buffer_snapshot
1268 .anchor_at(start.to_point(&snapshot.display_snapshot), Bias::Left)
1269 ..snapshot
1270 .buffer_snapshot
1271 .anchor_at(end.to_point(&snapshot.display_snapshot), Bias::Right);
1272
1273 let Some(selection) = snapshot.remote_selections_in_range(&range, hub, cx).next() else {
1274 return;
1275 };
1276 let key = crate::HoveredCursor {
1277 replica_id: selection.replica_id,
1278 selection_id: selection.selection.id,
1279 };
1280 editor.hovered_cursors.insert(
1281 key.clone(),
1282 cx.spawn_in(window, async move |editor, cx| {
1283 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
1284 editor
1285 .update(cx, |editor, cx| {
1286 editor.hovered_cursors.remove(&key);
1287 cx.notify();
1288 })
1289 .ok();
1290 }),
1291 );
1292 cx.notify()
1293 }
1294
1295 fn layout_selections(
1296 &self,
1297 start_anchor: Anchor,
1298 end_anchor: Anchor,
1299 local_selections: &[Selection<Point>],
1300 snapshot: &EditorSnapshot,
1301 start_row: DisplayRow,
1302 end_row: DisplayRow,
1303 window: &mut Window,
1304 cx: &mut App,
1305 ) -> (
1306 Vec<(PlayerColor, Vec<SelectionLayout>)>,
1307 BTreeMap<DisplayRow, LineHighlightSpec>,
1308 Option<DisplayPoint>,
1309 ) {
1310 let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
1311 let mut active_rows = BTreeMap::new();
1312 let mut newest_selection_head = None;
1313
1314 let Some(editor_with_selections) = self.editor_with_selections(cx) else {
1315 return (selections, active_rows, newest_selection_head);
1316 };
1317
1318 editor_with_selections.update(cx, |editor, cx| {
1319 if editor.show_local_selections {
1320 let mut layouts = Vec::new();
1321 let newest = editor.selections.newest(cx);
1322 for selection in local_selections.iter().cloned() {
1323 let is_empty = selection.start == selection.end;
1324 let is_newest = selection == newest;
1325
1326 let layout = SelectionLayout::new(
1327 selection,
1328 editor.selections.line_mode,
1329 editor.cursor_shape,
1330 &snapshot.display_snapshot,
1331 is_newest,
1332 editor.leader_id.is_none(),
1333 None,
1334 );
1335 if is_newest {
1336 newest_selection_head = Some(layout.head);
1337 }
1338
1339 for row in cmp::max(layout.active_rows.start.0, start_row.0)
1340 ..=cmp::min(layout.active_rows.end.0, end_row.0)
1341 {
1342 let contains_non_empty_selection = active_rows
1343 .entry(DisplayRow(row))
1344 .or_insert_with(LineHighlightSpec::default);
1345 contains_non_empty_selection.selection |= !is_empty;
1346 }
1347 layouts.push(layout);
1348 }
1349
1350 let player = editor.current_user_player_color(cx);
1351 selections.push((player, layouts));
1352
1353 if let SelectionDragState::Dragging {
1354 ref selection,
1355 ref drop_cursor,
1356 ref hide_drop_cursor,
1357 } = editor.selection_drag_state
1358 {
1359 if !hide_drop_cursor
1360 && (drop_cursor
1361 .start
1362 .cmp(&selection.start, &snapshot.buffer_snapshot)
1363 .eq(&Ordering::Less)
1364 || drop_cursor
1365 .end
1366 .cmp(&selection.end, &snapshot.buffer_snapshot)
1367 .eq(&Ordering::Greater))
1368 {
1369 let drag_cursor_layout = SelectionLayout::new(
1370 drop_cursor.clone(),
1371 false,
1372 CursorShape::Bar,
1373 &snapshot.display_snapshot,
1374 false,
1375 false,
1376 None,
1377 );
1378 let absent_color = cx.theme().players().absent();
1379 selections.push((absent_color, vec![drag_cursor_layout]));
1380 }
1381 }
1382 }
1383
1384 if let Some(collaboration_hub) = &editor.collaboration_hub {
1385 // When following someone, render the local selections in their color.
1386 if let Some(leader_id) = editor.leader_id {
1387 match leader_id {
1388 CollaboratorId::PeerId(peer_id) => {
1389 if let Some(collaborator) =
1390 collaboration_hub.collaborators(cx).get(&peer_id)
1391 {
1392 if let Some(participant_index) = collaboration_hub
1393 .user_participant_indices(cx)
1394 .get(&collaborator.user_id)
1395 {
1396 if let Some((local_selection_style, _)) = selections.first_mut()
1397 {
1398 *local_selection_style = cx
1399 .theme()
1400 .players()
1401 .color_for_participant(participant_index.0);
1402 }
1403 }
1404 }
1405 }
1406 CollaboratorId::Agent => {
1407 if let Some((local_selection_style, _)) = selections.first_mut() {
1408 *local_selection_style = cx.theme().players().agent();
1409 }
1410 }
1411 }
1412 }
1413
1414 let mut remote_selections = HashMap::default();
1415 for selection in snapshot.remote_selections_in_range(
1416 &(start_anchor..end_anchor),
1417 collaboration_hub.as_ref(),
1418 cx,
1419 ) {
1420 // Don't re-render the leader's selections, since the local selections
1421 // match theirs.
1422 if Some(selection.collaborator_id) == editor.leader_id {
1423 continue;
1424 }
1425 let key = HoveredCursor {
1426 replica_id: selection.replica_id,
1427 selection_id: selection.selection.id,
1428 };
1429
1430 let is_shown =
1431 editor.show_cursor_names || editor.hovered_cursors.contains_key(&key);
1432
1433 remote_selections
1434 .entry(selection.replica_id)
1435 .or_insert((selection.color, Vec::new()))
1436 .1
1437 .push(SelectionLayout::new(
1438 selection.selection,
1439 selection.line_mode,
1440 selection.cursor_shape,
1441 &snapshot.display_snapshot,
1442 false,
1443 false,
1444 if is_shown { selection.user_name } else { None },
1445 ));
1446 }
1447
1448 selections.extend(remote_selections.into_values());
1449 } else if !editor.is_focused(window) && editor.show_cursor_when_unfocused {
1450 let layouts = snapshot
1451 .buffer_snapshot
1452 .selections_in_range(&(start_anchor..end_anchor), true)
1453 .map(move |(_, line_mode, cursor_shape, selection)| {
1454 SelectionLayout::new(
1455 selection,
1456 line_mode,
1457 cursor_shape,
1458 &snapshot.display_snapshot,
1459 false,
1460 false,
1461 None,
1462 )
1463 })
1464 .collect::<Vec<_>>();
1465 let player = editor.current_user_player_color(cx);
1466 selections.push((player, layouts));
1467 }
1468 });
1469 (selections, active_rows, newest_selection_head)
1470 }
1471
1472 fn collect_cursors(
1473 &self,
1474 snapshot: &EditorSnapshot,
1475 cx: &mut App,
1476 ) -> Vec<(DisplayPoint, Hsla)> {
1477 let editor = self.editor.read(cx);
1478 let mut cursors = Vec::new();
1479 let mut skip_local = false;
1480 let mut add_cursor = |anchor: Anchor, color| {
1481 cursors.push((anchor.to_display_point(&snapshot.display_snapshot), color));
1482 };
1483 // Remote cursors
1484 if let Some(collaboration_hub) = &editor.collaboration_hub {
1485 for remote_selection in snapshot.remote_selections_in_range(
1486 &(Anchor::min()..Anchor::max()),
1487 collaboration_hub.deref(),
1488 cx,
1489 ) {
1490 add_cursor(
1491 remote_selection.selection.head(),
1492 remote_selection.color.cursor,
1493 );
1494 if Some(remote_selection.collaborator_id) == editor.leader_id {
1495 skip_local = true;
1496 }
1497 }
1498 }
1499 // Local cursors
1500 if !skip_local {
1501 let color = cx.theme().players().local().cursor;
1502 editor.selections.disjoint.iter().for_each(|selection| {
1503 add_cursor(selection.head(), color);
1504 });
1505 if let Some(ref selection) = editor.selections.pending_anchor() {
1506 add_cursor(selection.head(), color);
1507 }
1508 }
1509 cursors
1510 }
1511
1512 fn layout_visible_cursors(
1513 &self,
1514 snapshot: &EditorSnapshot,
1515 selections: &[(PlayerColor, Vec<SelectionLayout>)],
1516 row_block_types: &HashMap<DisplayRow, bool>,
1517 visible_display_row_range: Range<DisplayRow>,
1518 line_layouts: &[LineWithInvisibles],
1519 text_hitbox: &Hitbox,
1520 content_origin: gpui::Point<Pixels>,
1521 scroll_position: gpui::Point<f32>,
1522 scroll_pixel_position: gpui::Point<Pixels>,
1523 line_height: Pixels,
1524 em_width: Pixels,
1525 em_advance: Pixels,
1526 autoscroll_containing_element: bool,
1527 window: &mut Window,
1528 cx: &mut App,
1529 ) -> Vec<CursorLayout> {
1530 let mut autoscroll_bounds = None;
1531 let cursor_layouts = self.editor.update(cx, |editor, cx| {
1532 let mut cursors = Vec::new();
1533
1534 let show_local_cursors = editor.show_local_cursors(window, cx);
1535
1536 for (player_color, selections) in selections {
1537 for selection in selections {
1538 let cursor_position = selection.head;
1539
1540 let in_range = visible_display_row_range.contains(&cursor_position.row());
1541 if (selection.is_local && !show_local_cursors)
1542 || !in_range
1543 || row_block_types.get(&cursor_position.row()) == Some(&true)
1544 {
1545 continue;
1546 }
1547
1548 let cursor_row_layout = &line_layouts
1549 [cursor_position.row().minus(visible_display_row_range.start) as usize];
1550 let cursor_column = cursor_position.column() as usize;
1551
1552 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
1553 let mut block_width =
1554 cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
1555 if block_width == Pixels::ZERO {
1556 block_width = em_advance;
1557 }
1558 let block_text = if let CursorShape::Block = selection.cursor_shape {
1559 snapshot
1560 .grapheme_at(cursor_position)
1561 .or_else(|| {
1562 if cursor_column == 0 {
1563 snapshot.placeholder_text().and_then(|s| {
1564 s.graphemes(true).next().map(|s| s.to_string().into())
1565 })
1566 } else {
1567 None
1568 }
1569 })
1570 .map(|text| {
1571 let len = text.len();
1572
1573 let font = cursor_row_layout
1574 .font_id_for_index(cursor_column)
1575 .and_then(|cursor_font_id| {
1576 window.text_system().get_font_for_id(cursor_font_id)
1577 })
1578 .unwrap_or(self.style.text.font());
1579
1580 // Invert the text color for the block cursor. Ensure that the text
1581 // color is opaque enough to be visible against the background color.
1582 //
1583 // 0.75 is an arbitrary threshold to determine if the background color is
1584 // opaque enough to use as a text color.
1585 //
1586 // TODO: In the future we should ensure themes have a `text_inverse` color.
1587 let color = if cx.theme().colors().editor_background.a < 0.75 {
1588 match cx.theme().appearance {
1589 Appearance::Dark => Hsla::black(),
1590 Appearance::Light => Hsla::white(),
1591 }
1592 } else {
1593 cx.theme().colors().editor_background
1594 };
1595
1596 window.text_system().shape_line(
1597 text,
1598 cursor_row_layout.font_size,
1599 &[TextRun {
1600 len,
1601 font,
1602 color,
1603 background_color: None,
1604 strikethrough: None,
1605 underline: None,
1606 }],
1607 )
1608 })
1609 } else {
1610 None
1611 };
1612
1613 let x = cursor_character_x - scroll_pixel_position.x;
1614 let y = (cursor_position.row().as_f32()
1615 - scroll_pixel_position.y / line_height)
1616 * line_height;
1617 if selection.is_newest {
1618 editor.pixel_position_of_newest_cursor = Some(point(
1619 text_hitbox.origin.x + x + block_width / 2.,
1620 text_hitbox.origin.y + y + line_height / 2.,
1621 ));
1622
1623 if autoscroll_containing_element {
1624 let top = text_hitbox.origin.y
1625 + (cursor_position.row().as_f32() - scroll_position.y - 3.).max(0.)
1626 * line_height;
1627 let left = text_hitbox.origin.x
1628 + (cursor_position.column() as f32 - scroll_position.x - 3.)
1629 .max(0.)
1630 * em_width;
1631
1632 let bottom = text_hitbox.origin.y
1633 + (cursor_position.row().as_f32() - scroll_position.y + 4.)
1634 * line_height;
1635 let right = text_hitbox.origin.x
1636 + (cursor_position.column() as f32 - scroll_position.x + 4.)
1637 * em_width;
1638
1639 autoscroll_bounds =
1640 Some(Bounds::from_corners(point(left, top), point(right, bottom)))
1641 }
1642 }
1643
1644 let mut cursor = CursorLayout {
1645 color: player_color.cursor,
1646 block_width,
1647 origin: point(x, y),
1648 line_height,
1649 shape: selection.cursor_shape,
1650 block_text,
1651 cursor_name: None,
1652 };
1653 let cursor_name = selection.user_name.clone().map(|name| CursorName {
1654 string: name,
1655 color: self.style.background,
1656 is_top_row: cursor_position.row().0 == 0,
1657 });
1658 cursor.layout(content_origin, cursor_name, window, cx);
1659 cursors.push(cursor);
1660 }
1661 }
1662
1663 cursors
1664 });
1665
1666 if let Some(bounds) = autoscroll_bounds {
1667 window.request_autoscroll(bounds);
1668 }
1669
1670 cursor_layouts
1671 }
1672
1673 fn layout_scrollbars(
1674 &self,
1675 snapshot: &EditorSnapshot,
1676 scrollbar_layout_information: &ScrollbarLayoutInformation,
1677 content_offset: gpui::Point<Pixels>,
1678 scroll_position: gpui::Point<f32>,
1679 non_visible_cursors: bool,
1680 right_margin: Pixels,
1681 editor_width: Pixels,
1682 window: &mut Window,
1683 cx: &mut App,
1684 ) -> Option<EditorScrollbars> {
1685 let show_scrollbars = self.editor.read(cx).show_scrollbars;
1686 if (!show_scrollbars.horizontal && !show_scrollbars.vertical)
1687 || self.style.scrollbar_width.is_zero()
1688 {
1689 return None;
1690 }
1691
1692 // If a drag took place after we started dragging the scrollbar,
1693 // cancel the scrollbar drag.
1694 if cx.has_active_drag() {
1695 self.editor.update(cx, |editor, cx| {
1696 editor.scroll_manager.reset_scrollbar_state(cx)
1697 });
1698 }
1699
1700 let editor_settings = EditorSettings::get_global(cx);
1701 let scrollbar_settings = editor_settings.scrollbar;
1702 let show_scrollbars = match scrollbar_settings.show {
1703 ShowScrollbar::Auto => {
1704 let editor = self.editor.read(cx);
1705 let is_singleton = editor.is_singleton(cx);
1706 // Git
1707 (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_diff_hunks())
1708 ||
1709 // Buffer Search Results
1710 (is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
1711 ||
1712 // Selected Text Occurrences
1713 (is_singleton && scrollbar_settings.selected_text && editor.has_background_highlights::<SelectedTextHighlight>())
1714 ||
1715 // Selected Symbol Occurrences
1716 (is_singleton && scrollbar_settings.selected_symbol && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
1717 ||
1718 // Diagnostics
1719 (is_singleton && scrollbar_settings.diagnostics != ScrollbarDiagnostics::None && snapshot.buffer_snapshot.has_diagnostics())
1720 ||
1721 // Cursors out of sight
1722 non_visible_cursors
1723 ||
1724 // Scrollmanager
1725 editor.scroll_manager.scrollbars_visible()
1726 }
1727 ShowScrollbar::System => self.editor.read(cx).scroll_manager.scrollbars_visible(),
1728 ShowScrollbar::Always => true,
1729 ShowScrollbar::Never => return None,
1730 };
1731
1732 // The horizontal scrollbar is usually slightly offset to align nicely with
1733 // indent guides. However, this offset is not needed if indent guides are
1734 // disabled for the current editor.
1735 let content_offset = self
1736 .editor
1737 .read(cx)
1738 .show_indent_guides
1739 .is_none_or(|should_show| should_show)
1740 .then_some(content_offset)
1741 .unwrap_or_default();
1742
1743 Some(EditorScrollbars::from_scrollbar_axes(
1744 ScrollbarAxes {
1745 horizontal: scrollbar_settings.axes.horizontal
1746 && self.editor.read(cx).show_scrollbars.horizontal,
1747 vertical: scrollbar_settings.axes.vertical
1748 && self.editor.read(cx).show_scrollbars.vertical,
1749 },
1750 scrollbar_layout_information,
1751 content_offset,
1752 scroll_position,
1753 self.style.scrollbar_width,
1754 right_margin,
1755 editor_width,
1756 show_scrollbars,
1757 self.editor.read(cx).scroll_manager.active_scrollbar_state(),
1758 window,
1759 ))
1760 }
1761
1762 fn layout_minimap(
1763 &self,
1764 snapshot: &EditorSnapshot,
1765 minimap_width: Pixels,
1766 scroll_position: gpui::Point<f32>,
1767 scrollbar_layout_information: &ScrollbarLayoutInformation,
1768 scrollbar_layout: Option<&EditorScrollbars>,
1769 window: &mut Window,
1770 cx: &mut App,
1771 ) -> Option<MinimapLayout> {
1772 let minimap_editor = self.editor.read(cx).minimap().cloned()?;
1773
1774 let minimap_settings = EditorSettings::get_global(cx).minimap;
1775
1776 if !snapshot.mode.is_full()
1777 || minimap_width.is_zero()
1778 || matches!(
1779 minimap_settings.show,
1780 ShowMinimap::Auto if scrollbar_layout.is_none_or(|layout| !layout.visible)
1781 )
1782 {
1783 return None;
1784 }
1785
1786 const MINIMAP_AXIS: ScrollbarAxis = ScrollbarAxis::Vertical;
1787
1788 let ScrollbarLayoutInformation {
1789 editor_bounds,
1790 scroll_range,
1791 glyph_grid_cell,
1792 } = scrollbar_layout_information;
1793
1794 let line_height = glyph_grid_cell.height;
1795 let scroll_position = scroll_position.along(MINIMAP_AXIS);
1796
1797 let top_right_anchor = scrollbar_layout
1798 .and_then(|layout| layout.vertical.as_ref())
1799 .map(|vertical_scrollbar| vertical_scrollbar.hitbox.origin)
1800 .unwrap_or_else(|| editor_bounds.top_right());
1801
1802 let thumb_state = self
1803 .editor
1804 .read_with(cx, |editor, _| editor.scroll_manager.minimap_thumb_state());
1805
1806 let show_thumb = match minimap_settings.thumb {
1807 MinimapThumb::Always => true,
1808 MinimapThumb::Hover => thumb_state.is_some(),
1809 };
1810
1811 let minimap_bounds = Bounds::from_corner_and_size(
1812 Corner::TopRight,
1813 top_right_anchor,
1814 size(minimap_width, editor_bounds.size.height),
1815 );
1816 let minimap_line_height = self.get_minimap_line_height(
1817 minimap_editor
1818 .read(cx)
1819 .text_style_refinement
1820 .as_ref()
1821 .and_then(|refinement| refinement.font_size)
1822 .unwrap_or(MINIMAP_FONT_SIZE),
1823 window,
1824 cx,
1825 );
1826 let minimap_height = minimap_bounds.size.height;
1827
1828 let visible_editor_lines = editor_bounds.size.height / line_height;
1829 let total_editor_lines = scroll_range.height / line_height;
1830 let minimap_lines = minimap_height / minimap_line_height;
1831
1832 let minimap_scroll_top = MinimapLayout::calculate_minimap_top_offset(
1833 total_editor_lines,
1834 visible_editor_lines,
1835 minimap_lines,
1836 scroll_position,
1837 );
1838
1839 let layout = ScrollbarLayout::for_minimap(
1840 window.insert_hitbox(minimap_bounds, HitboxBehavior::Normal),
1841 visible_editor_lines,
1842 total_editor_lines,
1843 minimap_line_height,
1844 scroll_position,
1845 minimap_scroll_top,
1846 show_thumb,
1847 )
1848 .with_thumb_state(thumb_state);
1849
1850 minimap_editor.update(cx, |editor, cx| {
1851 editor.set_scroll_position(point(0., minimap_scroll_top), window, cx)
1852 });
1853
1854 // Required for the drop shadow to be visible
1855 const PADDING_OFFSET: Pixels = px(4.);
1856
1857 let mut minimap = div()
1858 .size_full()
1859 .shadow_sm()
1860 .px(PADDING_OFFSET)
1861 .child(minimap_editor)
1862 .into_any_element();
1863
1864 let extended_bounds = minimap_bounds.extend(Edges {
1865 right: PADDING_OFFSET,
1866 left: PADDING_OFFSET,
1867 ..Default::default()
1868 });
1869 minimap.layout_as_root(extended_bounds.size.into(), window, cx);
1870 window.with_absolute_element_offset(extended_bounds.origin, |window| {
1871 minimap.prepaint(window, cx)
1872 });
1873
1874 Some(MinimapLayout {
1875 minimap,
1876 thumb_layout: layout,
1877 thumb_border_style: minimap_settings.thumb_border,
1878 minimap_line_height,
1879 minimap_scroll_top,
1880 max_scroll_top: total_editor_lines,
1881 })
1882 }
1883
1884 fn get_minimap_line_height(
1885 &self,
1886 font_size: AbsoluteLength,
1887 window: &mut Window,
1888 cx: &mut App,
1889 ) -> Pixels {
1890 let rem_size = self.rem_size(cx).unwrap_or(window.rem_size());
1891 let mut text_style = self.style.text.clone();
1892 text_style.font_size = font_size;
1893 text_style.line_height_in_pixels(rem_size)
1894 }
1895
1896 fn prepaint_crease_toggles(
1897 &self,
1898 crease_toggles: &mut [Option<AnyElement>],
1899 line_height: Pixels,
1900 gutter_dimensions: &GutterDimensions,
1901 gutter_settings: crate::editor_settings::Gutter,
1902 scroll_pixel_position: gpui::Point<Pixels>,
1903 gutter_hitbox: &Hitbox,
1904 window: &mut Window,
1905 cx: &mut App,
1906 ) {
1907 for (ix, crease_toggle) in crease_toggles.iter_mut().enumerate() {
1908 if let Some(crease_toggle) = crease_toggle {
1909 debug_assert!(gutter_settings.folds);
1910 let available_space = size(
1911 AvailableSpace::MinContent,
1912 AvailableSpace::Definite(line_height * 0.55),
1913 );
1914 let crease_toggle_size = crease_toggle.layout_as_root(available_space, window, cx);
1915
1916 let position = point(
1917 gutter_dimensions.width - gutter_dimensions.right_padding,
1918 ix as f32 * line_height - (scroll_pixel_position.y % line_height),
1919 );
1920 let centering_offset = point(
1921 (gutter_dimensions.fold_area_width() - crease_toggle_size.width) / 2.,
1922 (line_height - crease_toggle_size.height) / 2.,
1923 );
1924 let origin = gutter_hitbox.origin + position + centering_offset;
1925 crease_toggle.prepaint_as_root(origin, available_space, window, cx);
1926 }
1927 }
1928 }
1929
1930 fn prepaint_expand_toggles(
1931 &self,
1932 expand_toggles: &mut [Option<(AnyElement, gpui::Point<Pixels>)>],
1933 window: &mut Window,
1934 cx: &mut App,
1935 ) {
1936 for (expand_toggle, origin) in expand_toggles.iter_mut().flatten() {
1937 let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
1938 expand_toggle.layout_as_root(available_space, window, cx);
1939 expand_toggle.prepaint_as_root(*origin, available_space, window, cx);
1940 }
1941 }
1942
1943 fn prepaint_crease_trailers(
1944 &self,
1945 trailers: Vec<Option<AnyElement>>,
1946 lines: &[LineWithInvisibles],
1947 line_height: Pixels,
1948 content_origin: gpui::Point<Pixels>,
1949 scroll_pixel_position: gpui::Point<Pixels>,
1950 em_width: Pixels,
1951 window: &mut Window,
1952 cx: &mut App,
1953 ) -> Vec<Option<CreaseTrailerLayout>> {
1954 trailers
1955 .into_iter()
1956 .enumerate()
1957 .map(|(ix, element)| {
1958 let mut element = element?;
1959 let available_space = size(
1960 AvailableSpace::MinContent,
1961 AvailableSpace::Definite(line_height),
1962 );
1963 let size = element.layout_as_root(available_space, window, cx);
1964
1965 let line = &lines[ix];
1966 let padding = if line.width == Pixels::ZERO {
1967 Pixels::ZERO
1968 } else {
1969 4. * em_width
1970 };
1971 let position = point(
1972 scroll_pixel_position.x + line.width + padding,
1973 ix as f32 * line_height - (scroll_pixel_position.y % line_height),
1974 );
1975 let centering_offset = point(px(0.), (line_height - size.height) / 2.);
1976 let origin = content_origin + position + centering_offset;
1977 element.prepaint_as_root(origin, available_space, window, cx);
1978 Some(CreaseTrailerLayout {
1979 element,
1980 bounds: Bounds::new(origin, size),
1981 })
1982 })
1983 .collect()
1984 }
1985
1986 // Folds contained in a hunk are ignored apart from shrinking visual size
1987 // If a fold contains any hunks then that fold line is marked as modified
1988 fn layout_gutter_diff_hunks(
1989 &self,
1990 line_height: Pixels,
1991 gutter_hitbox: &Hitbox,
1992 display_rows: Range<DisplayRow>,
1993 snapshot: &EditorSnapshot,
1994 window: &mut Window,
1995 cx: &mut App,
1996 ) -> Vec<(DisplayDiffHunk, Option<Hitbox>)> {
1997 let folded_buffers = self.editor.read(cx).folded_buffers(cx);
1998 let mut display_hunks = snapshot
1999 .display_diff_hunks_for_rows(display_rows, folded_buffers)
2000 .map(|hunk| (hunk, None))
2001 .collect::<Vec<_>>();
2002 let git_gutter_setting = ProjectSettings::get_global(cx)
2003 .git
2004 .git_gutter
2005 .unwrap_or_default();
2006 if let GitGutterSetting::TrackedFiles = git_gutter_setting {
2007 for (hunk, hitbox) in &mut display_hunks {
2008 if matches!(hunk, DisplayDiffHunk::Unfolded { .. }) {
2009 let hunk_bounds =
2010 Self::diff_hunk_bounds(snapshot, line_height, gutter_hitbox.bounds, hunk);
2011 *hitbox = Some(window.insert_hitbox(hunk_bounds, HitboxBehavior::BlockMouse));
2012 }
2013 }
2014 }
2015
2016 display_hunks
2017 }
2018
2019 fn layout_inline_diagnostics(
2020 &self,
2021 line_layouts: &[LineWithInvisibles],
2022 crease_trailers: &[Option<CreaseTrailerLayout>],
2023 row_block_types: &HashMap<DisplayRow, bool>,
2024 content_origin: gpui::Point<Pixels>,
2025 scroll_pixel_position: gpui::Point<Pixels>,
2026 inline_completion_popover_origin: Option<gpui::Point<Pixels>>,
2027 start_row: DisplayRow,
2028 end_row: DisplayRow,
2029 line_height: Pixels,
2030 em_width: Pixels,
2031 style: &EditorStyle,
2032 window: &mut Window,
2033 cx: &mut App,
2034 ) -> HashMap<DisplayRow, AnyElement> {
2035 if self.editor.read(cx).mode().is_minimap() {
2036 return HashMap::default();
2037 }
2038
2039 let max_severity = match ProjectSettings::get_global(cx)
2040 .diagnostics
2041 .inline
2042 .max_severity
2043 .unwrap_or_else(|| self.editor.read(cx).diagnostics_max_severity)
2044 .into_lsp()
2045 {
2046 Some(max_severity) => max_severity,
2047 None => return HashMap::default(),
2048 };
2049
2050 let active_diagnostics_group =
2051 if let ActiveDiagnostic::Group(group) = &self.editor.read(cx).active_diagnostics {
2052 Some(group.group_id)
2053 } else {
2054 None
2055 };
2056
2057 let diagnostics_by_rows = self.editor.update(cx, |editor, cx| {
2058 let snapshot = editor.snapshot(window, cx);
2059 editor
2060 .inline_diagnostics
2061 .iter()
2062 .filter(|(_, diagnostic)| diagnostic.severity <= max_severity)
2063 .filter(|(_, diagnostic)| match active_diagnostics_group {
2064 Some(active_diagnostics_group) => {
2065 // Active diagnostics are all shown in the editor already, no need to display them inline
2066 diagnostic.group_id != active_diagnostics_group
2067 }
2068 None => true,
2069 })
2070 .map(|(point, diag)| (point.to_display_point(&snapshot), diag.clone()))
2071 .skip_while(|(point, _)| point.row() < start_row)
2072 .take_while(|(point, _)| point.row() < end_row)
2073 .filter(|(point, _)| !row_block_types.contains_key(&point.row()))
2074 .fold(HashMap::default(), |mut acc, (point, diagnostic)| {
2075 acc.entry(point.row())
2076 .or_insert_with(Vec::new)
2077 .push(diagnostic);
2078 acc
2079 })
2080 });
2081
2082 if diagnostics_by_rows.is_empty() {
2083 return HashMap::default();
2084 }
2085
2086 let severity_to_color = |sev: &lsp::DiagnosticSeverity| match sev {
2087 &lsp::DiagnosticSeverity::ERROR => Color::Error,
2088 &lsp::DiagnosticSeverity::WARNING => Color::Warning,
2089 &lsp::DiagnosticSeverity::INFORMATION => Color::Info,
2090 &lsp::DiagnosticSeverity::HINT => Color::Hint,
2091 _ => Color::Error,
2092 };
2093
2094 let padding = ProjectSettings::get_global(cx).diagnostics.inline.padding as f32 * em_width;
2095 let min_x = ProjectSettings::get_global(cx)
2096 .diagnostics
2097 .inline
2098 .min_column as f32
2099 * em_width;
2100
2101 let mut elements = HashMap::default();
2102 for (row, mut diagnostics) in diagnostics_by_rows {
2103 diagnostics.sort_by_key(|diagnostic| {
2104 (
2105 diagnostic.severity,
2106 std::cmp::Reverse(diagnostic.is_primary),
2107 diagnostic.start.row,
2108 diagnostic.start.column,
2109 )
2110 });
2111
2112 let Some(diagnostic_to_render) = diagnostics
2113 .iter()
2114 .find(|diagnostic| diagnostic.is_primary)
2115 .or_else(|| diagnostics.first())
2116 else {
2117 continue;
2118 };
2119
2120 let pos_y = content_origin.y
2121 + line_height * (row.0 as f32 - scroll_pixel_position.y / line_height);
2122
2123 let window_ix = row.0.saturating_sub(start_row.0) as usize;
2124 let pos_x = {
2125 let crease_trailer_layout = &crease_trailers[window_ix];
2126 let line_layout = &line_layouts[window_ix];
2127
2128 let line_end = if let Some(crease_trailer) = crease_trailer_layout {
2129 crease_trailer.bounds.right()
2130 } else {
2131 content_origin.x - scroll_pixel_position.x + line_layout.width
2132 };
2133
2134 let padded_line = line_end + padding;
2135 let min_start = content_origin.x - scroll_pixel_position.x + min_x;
2136
2137 cmp::max(padded_line, min_start)
2138 };
2139
2140 let behind_inline_completion_popover = inline_completion_popover_origin
2141 .as_ref()
2142 .map_or(false, |inline_completion_popover_origin| {
2143 (pos_y..pos_y + line_height).contains(&inline_completion_popover_origin.y)
2144 });
2145 let opacity = if behind_inline_completion_popover {
2146 0.5
2147 } else {
2148 1.0
2149 };
2150
2151 let mut element = h_flex()
2152 .id(("diagnostic", row.0))
2153 .h(line_height)
2154 .w_full()
2155 .px_1()
2156 .rounded_xs()
2157 .opacity(opacity)
2158 .bg(severity_to_color(&diagnostic_to_render.severity)
2159 .color(cx)
2160 .opacity(0.05))
2161 .text_color(severity_to_color(&diagnostic_to_render.severity).color(cx))
2162 .text_sm()
2163 .font_family(style.text.font().family)
2164 .child(diagnostic_to_render.message.clone())
2165 .into_any();
2166
2167 element.prepaint_as_root(point(pos_x, pos_y), AvailableSpace::min_size(), window, cx);
2168
2169 elements.insert(row, element);
2170 }
2171
2172 elements
2173 }
2174
2175 fn layout_inline_code_actions(
2176 &self,
2177 display_point: DisplayPoint,
2178 content_origin: gpui::Point<Pixels>,
2179 scroll_pixel_position: gpui::Point<Pixels>,
2180 line_height: Pixels,
2181 snapshot: &EditorSnapshot,
2182 window: &mut Window,
2183 cx: &mut App,
2184 ) -> Option<AnyElement> {
2185 if !snapshot
2186 .show_code_actions
2187 .unwrap_or(EditorSettings::get_global(cx).inline_code_actions)
2188 {
2189 return None;
2190 }
2191
2192 let icon_size = ui::IconSize::XSmall;
2193 let mut button = self.editor.update(cx, |editor, cx| {
2194 editor.available_code_actions.as_ref()?;
2195 let active = editor
2196 .context_menu
2197 .borrow()
2198 .as_ref()
2199 .and_then(|menu| {
2200 if let crate::CodeContextMenu::CodeActions(CodeActionsMenu {
2201 deployed_from,
2202 ..
2203 }) = menu
2204 {
2205 deployed_from.as_ref()
2206 } else {
2207 None
2208 }
2209 })
2210 .map_or(false, |source| {
2211 matches!(source, CodeActionSource::Indicator(..))
2212 });
2213 Some(editor.render_inline_code_actions(icon_size, display_point.row(), active, cx))
2214 })?;
2215
2216 let buffer_point = display_point.to_point(&snapshot.display_snapshot);
2217
2218 // do not show code action for folded line
2219 if snapshot.is_line_folded(MultiBufferRow(buffer_point.row)) {
2220 return None;
2221 }
2222
2223 // do not show code action for blank line with cursor
2224 let line_indent = snapshot
2225 .display_snapshot
2226 .buffer_snapshot
2227 .line_indent_for_row(MultiBufferRow(buffer_point.row));
2228 if line_indent.is_line_blank() {
2229 return None;
2230 }
2231
2232 const INLINE_SLOT_CHAR_LIMIT: u32 = 4;
2233 const MAX_ALTERNATE_DISTANCE: u32 = 8;
2234
2235 let excerpt_id = snapshot
2236 .display_snapshot
2237 .buffer_snapshot
2238 .excerpt_containing(buffer_point..buffer_point)
2239 .map(|excerpt| excerpt.id());
2240
2241 let is_valid_row = |row_candidate: u32| -> bool {
2242 // move to other row if folded row
2243 if snapshot.is_line_folded(MultiBufferRow(row_candidate)) {
2244 return false;
2245 }
2246 if buffer_point.row == row_candidate {
2247 // move to other row if cursor is in slot
2248 if buffer_point.column < INLINE_SLOT_CHAR_LIMIT {
2249 return false;
2250 }
2251 } else {
2252 let candidate_point = MultiBufferPoint {
2253 row: row_candidate,
2254 column: 0,
2255 };
2256 let candidate_excerpt_id = snapshot
2257 .display_snapshot
2258 .buffer_snapshot
2259 .excerpt_containing(candidate_point..candidate_point)
2260 .map(|excerpt| excerpt.id());
2261 // move to other row if different excerpt
2262 if excerpt_id != candidate_excerpt_id {
2263 return false;
2264 }
2265 }
2266 let line_indent = snapshot
2267 .display_snapshot
2268 .buffer_snapshot
2269 .line_indent_for_row(MultiBufferRow(row_candidate));
2270 // use this row if it's blank
2271 if line_indent.is_line_blank() {
2272 true
2273 } else {
2274 // use this row if code starts after slot
2275 let indent_size = snapshot
2276 .display_snapshot
2277 .buffer_snapshot
2278 .indent_size_for_line(MultiBufferRow(row_candidate));
2279 indent_size.len >= INLINE_SLOT_CHAR_LIMIT
2280 }
2281 };
2282
2283 let new_buffer_row = if is_valid_row(buffer_point.row) {
2284 Some(buffer_point.row)
2285 } else {
2286 let max_row = snapshot.display_snapshot.buffer_snapshot.max_point().row;
2287 (1..=MAX_ALTERNATE_DISTANCE).find_map(|offset| {
2288 let row_above = buffer_point.row.saturating_sub(offset);
2289 let row_below = buffer_point.row + offset;
2290 if row_above != buffer_point.row && is_valid_row(row_above) {
2291 Some(row_above)
2292 } else if row_below <= max_row && is_valid_row(row_below) {
2293 Some(row_below)
2294 } else {
2295 None
2296 }
2297 })
2298 }?;
2299
2300 let new_display_row = snapshot
2301 .display_snapshot
2302 .point_to_display_point(
2303 Point {
2304 row: new_buffer_row,
2305 column: buffer_point.column,
2306 },
2307 text::Bias::Left,
2308 )
2309 .row();
2310
2311 let start_y = content_origin.y
2312 + ((new_display_row.as_f32() - (scroll_pixel_position.y / line_height)) * line_height)
2313 + (line_height / 2.0)
2314 - (icon_size.square(window, cx) / 2.);
2315 let start_x = content_origin.x - scroll_pixel_position.x + (window.rem_size() * 0.1);
2316
2317 let absolute_offset = gpui::point(start_x, start_y);
2318 button.layout_as_root(gpui::AvailableSpace::min_size(), window, cx);
2319 button.prepaint_as_root(
2320 absolute_offset,
2321 gpui::AvailableSpace::min_size(),
2322 window,
2323 cx,
2324 );
2325 Some(button)
2326 }
2327
2328 fn layout_inline_blame(
2329 &self,
2330 display_row: DisplayRow,
2331 row_info: &RowInfo,
2332 line_layout: &LineWithInvisibles,
2333 crease_trailer: Option<&CreaseTrailerLayout>,
2334 em_width: Pixels,
2335 content_origin: gpui::Point<Pixels>,
2336 scroll_pixel_position: gpui::Point<Pixels>,
2337 line_height: Pixels,
2338 text_hitbox: &Hitbox,
2339 window: &mut Window,
2340 cx: &mut App,
2341 ) -> Option<InlineBlameLayout> {
2342 if !self
2343 .editor
2344 .update(cx, |editor, cx| editor.render_git_blame_inline(window, cx))
2345 {
2346 return None;
2347 }
2348
2349 let editor = self.editor.read(cx);
2350 let blame = editor.blame.clone()?;
2351 let padding = {
2352 const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
2353 const INLINE_ACCEPT_SUGGESTION_EM_WIDTHS: f32 = 14.;
2354
2355 let mut padding = INLINE_BLAME_PADDING_EM_WIDTHS;
2356
2357 if let Some(inline_completion) = editor.active_inline_completion.as_ref() {
2358 match &inline_completion.completion {
2359 InlineCompletion::Edit {
2360 display_mode: EditDisplayMode::TabAccept,
2361 ..
2362 } => padding += INLINE_ACCEPT_SUGGESTION_EM_WIDTHS,
2363 _ => {}
2364 }
2365 }
2366
2367 padding * em_width
2368 };
2369
2370 let entry = blame
2371 .update(cx, |blame, cx| {
2372 blame.blame_for_rows(&[*row_info], cx).next()
2373 })
2374 .flatten()?;
2375
2376 let mut element = render_inline_blame_entry(entry.clone(), &self.style, cx)?;
2377
2378 let start_y = content_origin.y
2379 + line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height);
2380
2381 let start_x = {
2382 let line_end = if let Some(crease_trailer) = crease_trailer {
2383 crease_trailer.bounds.right()
2384 } else {
2385 content_origin.x - scroll_pixel_position.x + line_layout.width
2386 };
2387
2388 let padded_line_end = line_end + padding;
2389
2390 let min_column_in_pixels = ProjectSettings::get_global(cx)
2391 .git
2392 .inline_blame
2393 .and_then(|settings| settings.min_column)
2394 .map(|col| self.column_pixels(col as usize, window, cx))
2395 .unwrap_or(px(0.));
2396 let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels;
2397
2398 cmp::max(padded_line_end, min_start)
2399 };
2400
2401 let absolute_offset = point(start_x, start_y);
2402 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
2403 let bounds = Bounds::new(absolute_offset, size);
2404
2405 self.layout_blame_entry_popover(entry.clone(), blame, line_height, text_hitbox, window, cx);
2406
2407 element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), window, cx);
2408
2409 Some(InlineBlameLayout {
2410 element,
2411 bounds,
2412 entry,
2413 })
2414 }
2415
2416 fn layout_blame_entry_popover(
2417 &self,
2418 blame_entry: BlameEntry,
2419 blame: Entity<GitBlame>,
2420 line_height: Pixels,
2421 text_hitbox: &Hitbox,
2422 window: &mut Window,
2423 cx: &mut App,
2424 ) {
2425 let Some((popover_state, target_point)) = self.editor.read_with(cx, |editor, _| {
2426 editor
2427 .inline_blame_popover
2428 .as_ref()
2429 .map(|state| (state.popover_state.clone(), state.position))
2430 }) else {
2431 return;
2432 };
2433
2434 let workspace = self
2435 .editor
2436 .read_with(cx, |editor, _| editor.workspace().map(|w| w.downgrade()));
2437
2438 let maybe_element = workspace.and_then(|workspace| {
2439 render_blame_entry_popover(
2440 blame_entry,
2441 popover_state.scroll_handle,
2442 popover_state.commit_message,
2443 popover_state.markdown,
2444 workspace,
2445 &blame,
2446 window,
2447 cx,
2448 )
2449 });
2450
2451 if let Some(mut element) = maybe_element {
2452 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
2453 let overall_height = size.height + HOVER_POPOVER_GAP;
2454 let popover_origin = if target_point.y > overall_height {
2455 point(target_point.x, target_point.y - size.height)
2456 } else {
2457 point(
2458 target_point.x,
2459 target_point.y + line_height + HOVER_POPOVER_GAP,
2460 )
2461 };
2462
2463 let horizontal_offset = (text_hitbox.top_right().x
2464 - POPOVER_RIGHT_OFFSET
2465 - (popover_origin.x + size.width))
2466 .min(Pixels::ZERO);
2467
2468 let origin = point(popover_origin.x + horizontal_offset, popover_origin.y);
2469 let popover_bounds = Bounds::new(origin, size);
2470
2471 self.editor.update(cx, |editor, _| {
2472 if let Some(state) = &mut editor.inline_blame_popover {
2473 state.popover_bounds = Some(popover_bounds);
2474 }
2475 });
2476
2477 window.defer_draw(element, origin, 2);
2478 }
2479 }
2480
2481 fn layout_blame_entries(
2482 &self,
2483 buffer_rows: &[RowInfo],
2484 em_width: Pixels,
2485 scroll_position: gpui::Point<f32>,
2486 line_height: Pixels,
2487 gutter_hitbox: &Hitbox,
2488 max_width: Option<Pixels>,
2489 window: &mut Window,
2490 cx: &mut App,
2491 ) -> Option<Vec<AnyElement>> {
2492 if !self
2493 .editor
2494 .update(cx, |editor, cx| editor.render_git_blame_gutter(cx))
2495 {
2496 return None;
2497 }
2498
2499 let blame = self.editor.read(cx).blame.clone()?;
2500 let workspace = self.editor.read(cx).workspace()?;
2501 let blamed_rows: Vec<_> = blame.update(cx, |blame, cx| {
2502 blame.blame_for_rows(buffer_rows, cx).collect()
2503 });
2504
2505 let width = if let Some(max_width) = max_width {
2506 AvailableSpace::Definite(max_width)
2507 } else {
2508 AvailableSpace::MaxContent
2509 };
2510 let scroll_top = scroll_position.y * line_height;
2511 let start_x = em_width;
2512
2513 let mut last_used_color: Option<(PlayerColor, Oid)> = None;
2514 let blame_renderer = cx.global::<GlobalBlameRenderer>().0.clone();
2515
2516 let shaped_lines = blamed_rows
2517 .into_iter()
2518 .enumerate()
2519 .flat_map(|(ix, blame_entry)| {
2520 let mut element = render_blame_entry(
2521 ix,
2522 &blame,
2523 blame_entry?,
2524 &self.style,
2525 &mut last_used_color,
2526 self.editor.clone(),
2527 workspace.clone(),
2528 blame_renderer.clone(),
2529 cx,
2530 )?;
2531
2532 let start_y = ix as f32 * line_height - (scroll_top % line_height);
2533 let absolute_offset = gutter_hitbox.origin + point(start_x, start_y);
2534
2535 element.prepaint_as_root(
2536 absolute_offset,
2537 size(width, AvailableSpace::MinContent),
2538 window,
2539 cx,
2540 );
2541
2542 Some(element)
2543 })
2544 .collect();
2545
2546 Some(shaped_lines)
2547 }
2548
2549 fn layout_indent_guides(
2550 &self,
2551 content_origin: gpui::Point<Pixels>,
2552 text_origin: gpui::Point<Pixels>,
2553 visible_buffer_range: Range<MultiBufferRow>,
2554 scroll_pixel_position: gpui::Point<Pixels>,
2555 line_height: Pixels,
2556 snapshot: &DisplaySnapshot,
2557 window: &mut Window,
2558 cx: &mut App,
2559 ) -> Option<Vec<IndentGuideLayout>> {
2560 if self.editor.read(cx).mode().is_minimap() {
2561 return None;
2562 }
2563 let indent_guides = self.editor.update(cx, |editor, cx| {
2564 editor.indent_guides(visible_buffer_range, snapshot, cx)
2565 })?;
2566
2567 let active_indent_guide_indices = self.editor.update(cx, |editor, cx| {
2568 editor
2569 .find_active_indent_guide_indices(&indent_guides, snapshot, window, cx)
2570 .unwrap_or_default()
2571 });
2572
2573 Some(
2574 indent_guides
2575 .into_iter()
2576 .enumerate()
2577 .filter_map(|(i, indent_guide)| {
2578 let single_indent_width =
2579 self.column_pixels(indent_guide.tab_size as usize, window, cx);
2580 let total_width = single_indent_width * indent_guide.depth as f32;
2581 let start_x = content_origin.x + total_width - scroll_pixel_position.x;
2582 if start_x >= text_origin.x {
2583 let (offset_y, length) = Self::calculate_indent_guide_bounds(
2584 indent_guide.start_row..indent_guide.end_row,
2585 line_height,
2586 snapshot,
2587 );
2588
2589 let start_y = content_origin.y + offset_y - scroll_pixel_position.y;
2590
2591 Some(IndentGuideLayout {
2592 origin: point(start_x, start_y),
2593 length,
2594 single_indent_width,
2595 depth: indent_guide.depth,
2596 active: active_indent_guide_indices.contains(&i),
2597 settings: indent_guide.settings,
2598 })
2599 } else {
2600 None
2601 }
2602 })
2603 .collect(),
2604 )
2605 }
2606
2607 fn calculate_indent_guide_bounds(
2608 row_range: Range<MultiBufferRow>,
2609 line_height: Pixels,
2610 snapshot: &DisplaySnapshot,
2611 ) -> (gpui::Pixels, gpui::Pixels) {
2612 let start_point = Point::new(row_range.start.0, 0);
2613 let end_point = Point::new(row_range.end.0, 0);
2614
2615 let row_range = start_point.to_display_point(snapshot).row()
2616 ..end_point.to_display_point(snapshot).row();
2617
2618 let mut prev_line = start_point;
2619 prev_line.row = prev_line.row.saturating_sub(1);
2620 let prev_line = prev_line.to_display_point(snapshot).row();
2621
2622 let mut cons_line = end_point;
2623 cons_line.row += 1;
2624 let cons_line = cons_line.to_display_point(snapshot).row();
2625
2626 let mut offset_y = row_range.start.0 as f32 * line_height;
2627 let mut length = (cons_line.0.saturating_sub(row_range.start.0)) as f32 * line_height;
2628
2629 // If we are at the end of the buffer, ensure that the indent guide extends to the end of the line.
2630 if row_range.end == cons_line {
2631 length += line_height;
2632 }
2633
2634 // If there is a block (e.g. diagnostic) in between the start of the indent guide and the line above,
2635 // we want to extend the indent guide to the start of the block.
2636 let mut block_height = 0;
2637 let mut block_offset = 0;
2638 let mut found_excerpt_header = false;
2639 for (_, block) in snapshot.blocks_in_range(prev_line..row_range.start) {
2640 if matches!(block, Block::ExcerptBoundary { .. }) {
2641 found_excerpt_header = true;
2642 break;
2643 }
2644 block_offset += block.height();
2645 block_height += block.height();
2646 }
2647 if !found_excerpt_header {
2648 offset_y -= block_offset as f32 * line_height;
2649 length += block_height as f32 * line_height;
2650 }
2651
2652 // If there is a block (e.g. diagnostic) at the end of an multibuffer excerpt,
2653 // we want to ensure that the indent guide stops before the excerpt header.
2654 let mut block_height = 0;
2655 let mut found_excerpt_header = false;
2656 for (_, block) in snapshot.blocks_in_range(row_range.end..cons_line) {
2657 if matches!(block, Block::ExcerptBoundary { .. }) {
2658 found_excerpt_header = true;
2659 }
2660 block_height += block.height();
2661 }
2662 if found_excerpt_header {
2663 length -= block_height as f32 * line_height;
2664 }
2665
2666 (offset_y, length)
2667 }
2668
2669 fn layout_breakpoints(
2670 &self,
2671 line_height: Pixels,
2672 range: Range<DisplayRow>,
2673 scroll_pixel_position: gpui::Point<Pixels>,
2674 gutter_dimensions: &GutterDimensions,
2675 gutter_hitbox: &Hitbox,
2676 display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
2677 snapshot: &EditorSnapshot,
2678 breakpoints: HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)>,
2679 row_infos: &[RowInfo],
2680 window: &mut Window,
2681 cx: &mut App,
2682 ) -> Vec<AnyElement> {
2683 self.editor.update(cx, |editor, cx| {
2684 breakpoints
2685 .into_iter()
2686 .filter_map(|(display_row, (text_anchor, bp, state))| {
2687 if row_infos
2688 .get((display_row.0.saturating_sub(range.start.0)) as usize)
2689 .is_some_and(|row_info| {
2690 row_info.expand_info.is_some()
2691 || row_info
2692 .diff_status
2693 .is_some_and(|status| status.is_deleted())
2694 })
2695 {
2696 return None;
2697 }
2698
2699 if range.start > display_row || range.end < display_row {
2700 return None;
2701 }
2702
2703 let row =
2704 MultiBufferRow(DisplayPoint::new(display_row, 0).to_point(&snapshot).row);
2705 if snapshot.is_line_folded(row) {
2706 return None;
2707 }
2708
2709 let button = editor.render_breakpoint(text_anchor, display_row, &bp, state, cx);
2710
2711 let button = prepaint_gutter_button(
2712 button,
2713 display_row,
2714 line_height,
2715 gutter_dimensions,
2716 scroll_pixel_position,
2717 gutter_hitbox,
2718 display_hunks,
2719 window,
2720 cx,
2721 );
2722 Some(button)
2723 })
2724 .collect_vec()
2725 })
2726 }
2727
2728 #[allow(clippy::too_many_arguments)]
2729 fn layout_run_indicators(
2730 &self,
2731 line_height: Pixels,
2732 range: Range<DisplayRow>,
2733 row_infos: &[RowInfo],
2734 scroll_pixel_position: gpui::Point<Pixels>,
2735 gutter_dimensions: &GutterDimensions,
2736 gutter_hitbox: &Hitbox,
2737 display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
2738 snapshot: &EditorSnapshot,
2739 breakpoints: &mut HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)>,
2740 window: &mut Window,
2741 cx: &mut App,
2742 ) -> Vec<AnyElement> {
2743 self.editor.update(cx, |editor, cx| {
2744 let active_task_indicator_row =
2745 if let Some(crate::CodeContextMenu::CodeActions(CodeActionsMenu {
2746 deployed_from,
2747 actions,
2748 ..
2749 })) = editor.context_menu.borrow().as_ref()
2750 {
2751 actions
2752 .tasks()
2753 .map(|tasks| tasks.position.to_display_point(snapshot).row())
2754 .or_else(|| match deployed_from {
2755 Some(CodeActionSource::Indicator(row)) => Some(*row),
2756 _ => None,
2757 })
2758 } else {
2759 None
2760 };
2761
2762 let offset_range_start =
2763 snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left);
2764
2765 let offset_range_end =
2766 snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
2767
2768 editor
2769 .tasks
2770 .iter()
2771 .filter_map(|(_, tasks)| {
2772 let multibuffer_point = tasks.offset.to_point(&snapshot.buffer_snapshot);
2773 if multibuffer_point < offset_range_start
2774 || multibuffer_point > offset_range_end
2775 {
2776 return None;
2777 }
2778 let multibuffer_row = MultiBufferRow(multibuffer_point.row);
2779 let buffer_folded = snapshot
2780 .buffer_snapshot
2781 .buffer_line_for_row(multibuffer_row)
2782 .map(|(buffer_snapshot, _)| buffer_snapshot.remote_id())
2783 .map(|buffer_id| editor.is_buffer_folded(buffer_id, cx))
2784 .unwrap_or(false);
2785 if buffer_folded {
2786 return None;
2787 }
2788
2789 if snapshot.is_line_folded(multibuffer_row) {
2790 // Skip folded indicators, unless it's the starting line of a fold.
2791 if multibuffer_row
2792 .0
2793 .checked_sub(1)
2794 .map_or(false, |previous_row| {
2795 snapshot.is_line_folded(MultiBufferRow(previous_row))
2796 })
2797 {
2798 return None;
2799 }
2800 }
2801
2802 let display_row = multibuffer_point.to_display_point(snapshot).row();
2803 if !range.contains(&display_row) {
2804 return None;
2805 }
2806 if row_infos
2807 .get((display_row - range.start).0 as usize)
2808 .is_some_and(|row_info| row_info.expand_info.is_some())
2809 {
2810 return None;
2811 }
2812
2813 let button = editor.render_run_indicator(
2814 &self.style,
2815 Some(display_row) == active_task_indicator_row,
2816 display_row,
2817 breakpoints.remove(&display_row),
2818 cx,
2819 );
2820
2821 let button = prepaint_gutter_button(
2822 button,
2823 display_row,
2824 line_height,
2825 gutter_dimensions,
2826 scroll_pixel_position,
2827 gutter_hitbox,
2828 display_hunks,
2829 window,
2830 cx,
2831 );
2832 Some(button)
2833 })
2834 .collect_vec()
2835 })
2836 }
2837
2838 fn layout_expand_toggles(
2839 &self,
2840 gutter_hitbox: &Hitbox,
2841 gutter_dimensions: GutterDimensions,
2842 em_width: Pixels,
2843 line_height: Pixels,
2844 scroll_position: gpui::Point<f32>,
2845 buffer_rows: &[RowInfo],
2846 window: &mut Window,
2847 cx: &mut App,
2848 ) -> Vec<Option<(AnyElement, gpui::Point<Pixels>)>> {
2849 if self.editor.read(cx).disable_expand_excerpt_buttons {
2850 return vec![];
2851 }
2852
2853 let editor_font_size = self.style.text.font_size.to_pixels(window.rem_size()) * 1.2;
2854
2855 let scroll_top = scroll_position.y * line_height;
2856
2857 let max_line_number_length = self
2858 .editor
2859 .read(cx)
2860 .buffer()
2861 .read(cx)
2862 .snapshot(cx)
2863 .widest_line_number()
2864 .ilog10()
2865 + 1;
2866
2867 let elements = buffer_rows
2868 .into_iter()
2869 .enumerate()
2870 .map(|(ix, row_info)| {
2871 let ExpandInfo {
2872 excerpt_id,
2873 direction,
2874 } = row_info.expand_info?;
2875
2876 let icon_name = match direction {
2877 ExpandExcerptDirection::Up => IconName::ExpandUp,
2878 ExpandExcerptDirection::Down => IconName::ExpandDown,
2879 ExpandExcerptDirection::UpAndDown => IconName::ExpandVertical,
2880 };
2881
2882 let git_gutter_width = Self::gutter_strip_width(line_height);
2883 let available_width = gutter_dimensions.left_padding - git_gutter_width;
2884
2885 let editor = self.editor.clone();
2886 let is_wide = max_line_number_length
2887 >= EditorSettings::get_global(cx).gutter.min_line_number_digits as u32
2888 && row_info
2889 .buffer_row
2890 .is_some_and(|row| (row + 1).ilog10() + 1 == max_line_number_length)
2891 || gutter_dimensions.right_padding == px(0.);
2892
2893 let width = if is_wide {
2894 available_width - px(2.)
2895 } else {
2896 available_width + em_width - px(2.)
2897 };
2898
2899 let toggle = IconButton::new(("expand", ix), icon_name)
2900 .icon_color(Color::Custom(cx.theme().colors().editor_line_number))
2901 .selected_icon_color(Color::Custom(cx.theme().colors().editor_foreground))
2902 .icon_size(IconSize::Custom(rems(editor_font_size / window.rem_size())))
2903 .width(width.into())
2904 .on_click(move |_, window, cx| {
2905 editor.update(cx, |editor, cx| {
2906 editor.expand_excerpt(excerpt_id, direction, window, cx);
2907 });
2908 })
2909 .tooltip(Tooltip::for_action_title(
2910 "Expand Excerpt",
2911 &crate::actions::ExpandExcerpts::default(),
2912 ))
2913 .into_any_element();
2914
2915 let position = point(
2916 git_gutter_width + px(1.),
2917 ix as f32 * line_height - (scroll_top % line_height) + px(1.),
2918 );
2919 let origin = gutter_hitbox.origin + position;
2920
2921 Some((toggle, origin))
2922 })
2923 .collect();
2924
2925 elements
2926 }
2927
2928 fn calculate_relative_line_numbers(
2929 &self,
2930 snapshot: &EditorSnapshot,
2931 rows: &Range<DisplayRow>,
2932 relative_to: Option<DisplayRow>,
2933 ) -> HashMap<DisplayRow, DisplayRowDelta> {
2934 let mut relative_rows: HashMap<DisplayRow, DisplayRowDelta> = Default::default();
2935 let Some(relative_to) = relative_to else {
2936 return relative_rows;
2937 };
2938
2939 let start = rows.start.min(relative_to);
2940 let end = rows.end.max(relative_to);
2941
2942 let buffer_rows = snapshot
2943 .row_infos(start)
2944 .take(1 + end.minus(start) as usize)
2945 .collect::<Vec<_>>();
2946
2947 let head_idx = relative_to.minus(start);
2948 let mut delta = 1;
2949 let mut i = head_idx + 1;
2950 while i < buffer_rows.len() as u32 {
2951 if buffer_rows[i as usize].buffer_row.is_some() {
2952 if rows.contains(&DisplayRow(i + start.0)) {
2953 relative_rows.insert(DisplayRow(i + start.0), delta);
2954 }
2955 delta += 1;
2956 }
2957 i += 1;
2958 }
2959 delta = 1;
2960 i = head_idx.min(buffer_rows.len() as u32 - 1);
2961 while i > 0 && buffer_rows[i as usize].buffer_row.is_none() {
2962 i -= 1;
2963 }
2964
2965 while i > 0 {
2966 i -= 1;
2967 if buffer_rows[i as usize].buffer_row.is_some() {
2968 if rows.contains(&DisplayRow(i + start.0)) {
2969 relative_rows.insert(DisplayRow(i + start.0), delta);
2970 }
2971 delta += 1;
2972 }
2973 }
2974
2975 relative_rows
2976 }
2977
2978 fn layout_line_numbers(
2979 &self,
2980 gutter_hitbox: Option<&Hitbox>,
2981 gutter_dimensions: GutterDimensions,
2982 line_height: Pixels,
2983 scroll_position: gpui::Point<f32>,
2984 rows: Range<DisplayRow>,
2985 buffer_rows: &[RowInfo],
2986 active_rows: &BTreeMap<DisplayRow, LineHighlightSpec>,
2987 newest_selection_head: Option<DisplayPoint>,
2988 snapshot: &EditorSnapshot,
2989 window: &mut Window,
2990 cx: &mut App,
2991 ) -> Arc<HashMap<MultiBufferRow, LineNumberLayout>> {
2992 let include_line_numbers = snapshot.show_line_numbers.unwrap_or_else(|| {
2993 EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode.is_full()
2994 });
2995 if !include_line_numbers {
2996 return Arc::default();
2997 }
2998
2999 let (newest_selection_head, is_relative) = self.editor.update(cx, |editor, cx| {
3000 let newest_selection_head = newest_selection_head.unwrap_or_else(|| {
3001 let newest = editor.selections.newest::<Point>(cx);
3002 SelectionLayout::new(
3003 newest,
3004 editor.selections.line_mode,
3005 editor.cursor_shape,
3006 &snapshot.display_snapshot,
3007 true,
3008 true,
3009 None,
3010 )
3011 .head
3012 });
3013 let is_relative = editor.should_use_relative_line_numbers(cx);
3014 (newest_selection_head, is_relative)
3015 });
3016
3017 let relative_to = if is_relative {
3018 Some(newest_selection_head.row())
3019 } else {
3020 None
3021 };
3022 let relative_rows = self.calculate_relative_line_numbers(snapshot, &rows, relative_to);
3023 let mut line_number = String::new();
3024 let line_numbers = buffer_rows
3025 .into_iter()
3026 .enumerate()
3027 .flat_map(|(ix, row_info)| {
3028 let display_row = DisplayRow(rows.start.0 + ix as u32);
3029 line_number.clear();
3030 let non_relative_number = row_info.buffer_row? + 1;
3031 let number = relative_rows
3032 .get(&display_row)
3033 .unwrap_or(&non_relative_number);
3034 write!(&mut line_number, "{number}").unwrap();
3035 if row_info
3036 .diff_status
3037 .is_some_and(|status| status.is_deleted())
3038 {
3039 return None;
3040 }
3041
3042 let color = active_rows
3043 .get(&display_row)
3044 .map(|spec| {
3045 if spec.breakpoint {
3046 cx.theme().colors().debugger_accent
3047 } else {
3048 cx.theme().colors().editor_active_line_number
3049 }
3050 })
3051 .unwrap_or_else(|| cx.theme().colors().editor_line_number);
3052 let shaped_line =
3053 self.shape_line_number(SharedString::from(&line_number), color, window);
3054 let scroll_top = scroll_position.y * line_height;
3055 let line_origin = gutter_hitbox.map(|hitbox| {
3056 hitbox.origin
3057 + point(
3058 hitbox.size.width - shaped_line.width - gutter_dimensions.right_padding,
3059 ix as f32 * line_height - (scroll_top % line_height),
3060 )
3061 });
3062
3063 #[cfg(not(test))]
3064 let hitbox = line_origin.map(|line_origin| {
3065 window.insert_hitbox(
3066 Bounds::new(line_origin, size(shaped_line.width, line_height)),
3067 HitboxBehavior::Normal,
3068 )
3069 });
3070 #[cfg(test)]
3071 let hitbox = {
3072 let _ = line_origin;
3073 None
3074 };
3075
3076 let multi_buffer_row = DisplayPoint::new(display_row, 0).to_point(snapshot).row;
3077 let multi_buffer_row = MultiBufferRow(multi_buffer_row);
3078 let line_number = LineNumberLayout {
3079 shaped_line,
3080 hitbox,
3081 };
3082 Some((multi_buffer_row, line_number))
3083 })
3084 .collect();
3085 Arc::new(line_numbers)
3086 }
3087
3088 fn layout_crease_toggles(
3089 &self,
3090 rows: Range<DisplayRow>,
3091 row_infos: &[RowInfo],
3092 active_rows: &BTreeMap<DisplayRow, LineHighlightSpec>,
3093 snapshot: &EditorSnapshot,
3094 window: &mut Window,
3095 cx: &mut App,
3096 ) -> Vec<Option<AnyElement>> {
3097 let include_fold_statuses = EditorSettings::get_global(cx).gutter.folds
3098 && snapshot.mode.is_full()
3099 && self.editor.read(cx).is_singleton(cx);
3100 if include_fold_statuses {
3101 row_infos
3102 .into_iter()
3103 .enumerate()
3104 .map(|(ix, info)| {
3105 if info.expand_info.is_some() {
3106 return None;
3107 }
3108 let row = info.multibuffer_row?;
3109 let display_row = DisplayRow(rows.start.0 + ix as u32);
3110 let active = active_rows.contains_key(&display_row);
3111
3112 snapshot.render_crease_toggle(row, active, self.editor.clone(), window, cx)
3113 })
3114 .collect()
3115 } else {
3116 Vec::new()
3117 }
3118 }
3119
3120 fn layout_crease_trailers(
3121 &self,
3122 buffer_rows: impl IntoIterator<Item = RowInfo>,
3123 snapshot: &EditorSnapshot,
3124 window: &mut Window,
3125 cx: &mut App,
3126 ) -> Vec<Option<AnyElement>> {
3127 buffer_rows
3128 .into_iter()
3129 .map(|row_info| {
3130 if row_info.expand_info.is_some() {
3131 return None;
3132 }
3133 if let Some(row) = row_info.multibuffer_row {
3134 snapshot.render_crease_trailer(row, window, cx)
3135 } else {
3136 None
3137 }
3138 })
3139 .collect()
3140 }
3141
3142 fn layout_lines(
3143 rows: Range<DisplayRow>,
3144 snapshot: &EditorSnapshot,
3145 style: &EditorStyle,
3146 editor_width: Pixels,
3147 is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
3148 window: &mut Window,
3149 cx: &mut App,
3150 ) -> Vec<LineWithInvisibles> {
3151 if rows.start >= rows.end {
3152 return Vec::new();
3153 }
3154
3155 // Show the placeholder when the editor is empty
3156 if snapshot.is_empty() {
3157 let font_size = style.text.font_size.to_pixels(window.rem_size());
3158 let placeholder_color = cx.theme().colors().text_placeholder;
3159 let placeholder_text = snapshot.placeholder_text();
3160
3161 let placeholder_lines = placeholder_text
3162 .as_ref()
3163 .map_or("", AsRef::as_ref)
3164 .split('\n')
3165 .skip(rows.start.0 as usize)
3166 .chain(iter::repeat(""))
3167 .take(rows.len());
3168 placeholder_lines
3169 .map(move |line| {
3170 let run = TextRun {
3171 len: line.len(),
3172 font: style.text.font(),
3173 color: placeholder_color,
3174 background_color: None,
3175 underline: None,
3176 strikethrough: None,
3177 };
3178 let line =
3179 window
3180 .text_system()
3181 .shape_line(line.to_string().into(), font_size, &[run]);
3182 LineWithInvisibles {
3183 width: line.width,
3184 len: line.len,
3185 fragments: smallvec![LineFragment::Text(line)],
3186 invisibles: Vec::new(),
3187 font_size,
3188 }
3189 })
3190 .collect()
3191 } else {
3192 let chunks = snapshot.highlighted_chunks(rows.clone(), true, style);
3193 LineWithInvisibles::from_chunks(
3194 chunks,
3195 &style,
3196 MAX_LINE_LEN,
3197 rows.len(),
3198 &snapshot.mode,
3199 editor_width,
3200 is_row_soft_wrapped,
3201 window,
3202 cx,
3203 )
3204 }
3205 }
3206
3207 fn prepaint_lines(
3208 &self,
3209 start_row: DisplayRow,
3210 line_layouts: &mut [LineWithInvisibles],
3211 line_height: Pixels,
3212 scroll_pixel_position: gpui::Point<Pixels>,
3213 content_origin: gpui::Point<Pixels>,
3214 window: &mut Window,
3215 cx: &mut App,
3216 ) -> SmallVec<[AnyElement; 1]> {
3217 let mut line_elements = SmallVec::new();
3218 for (ix, line) in line_layouts.iter_mut().enumerate() {
3219 let row = start_row + DisplayRow(ix as u32);
3220 line.prepaint(
3221 line_height,
3222 scroll_pixel_position,
3223 row,
3224 content_origin,
3225 &mut line_elements,
3226 window,
3227 cx,
3228 );
3229 }
3230 line_elements
3231 }
3232
3233 fn render_block(
3234 &self,
3235 block: &Block,
3236 available_width: AvailableSpace,
3237 block_id: BlockId,
3238 block_row_start: DisplayRow,
3239 snapshot: &EditorSnapshot,
3240 text_x: Pixels,
3241 rows: &Range<DisplayRow>,
3242 line_layouts: &[LineWithInvisibles],
3243 editor_margins: &EditorMargins,
3244 line_height: Pixels,
3245 em_width: Pixels,
3246 text_hitbox: &Hitbox,
3247 editor_width: Pixels,
3248 scroll_width: &mut Pixels,
3249 resized_blocks: &mut HashMap<CustomBlockId, u32>,
3250 row_block_types: &mut HashMap<DisplayRow, bool>,
3251 selections: &[Selection<Point>],
3252 selected_buffer_ids: &Vec<BufferId>,
3253 is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
3254 sticky_header_excerpt_id: Option<ExcerptId>,
3255 window: &mut Window,
3256 cx: &mut App,
3257 ) -> Option<(AnyElement, Size<Pixels>, DisplayRow, Pixels)> {
3258 let mut x_position = None;
3259 let mut element = match block {
3260 Block::Custom(custom) => {
3261 let block_start = custom.start().to_point(&snapshot.buffer_snapshot);
3262 let block_end = custom.end().to_point(&snapshot.buffer_snapshot);
3263 if block.place_near() && snapshot.is_line_folded(MultiBufferRow(block_start.row)) {
3264 return None;
3265 }
3266 let align_to = block_start.to_display_point(snapshot);
3267 let x_and_width = |layout: &LineWithInvisibles| {
3268 Some((
3269 text_x + layout.x_for_index(align_to.column() as usize),
3270 text_x + layout.width,
3271 ))
3272 };
3273 let line_ix = align_to.row().0.checked_sub(rows.start.0);
3274 x_position =
3275 if let Some(layout) = line_ix.and_then(|ix| line_layouts.get(ix as usize)) {
3276 x_and_width(&layout)
3277 } else {
3278 x_and_width(&layout_line(
3279 align_to.row(),
3280 snapshot,
3281 &self.style,
3282 editor_width,
3283 is_row_soft_wrapped,
3284 window,
3285 cx,
3286 ))
3287 };
3288
3289 let anchor_x = x_position.unwrap().0;
3290
3291 let selected = selections
3292 .binary_search_by(|selection| {
3293 if selection.end <= block_start {
3294 Ordering::Less
3295 } else if selection.start >= block_end {
3296 Ordering::Greater
3297 } else {
3298 Ordering::Equal
3299 }
3300 })
3301 .is_ok();
3302
3303 div()
3304 .size_full()
3305 .children(
3306 (!snapshot.mode.is_minimap() || custom.render_in_minimap).then(|| {
3307 custom.render(&mut BlockContext {
3308 window,
3309 app: cx,
3310 anchor_x,
3311 margins: editor_margins,
3312 line_height,
3313 em_width,
3314 block_id,
3315 selected,
3316 max_width: text_hitbox.size.width.max(*scroll_width),
3317 editor_style: &self.style,
3318 })
3319 }),
3320 )
3321 .into_any()
3322 }
3323
3324 Block::FoldedBuffer {
3325 first_excerpt,
3326 height,
3327 ..
3328 } => {
3329 let selected = selected_buffer_ids.contains(&first_excerpt.buffer_id);
3330 let result = v_flex().id(block_id).w_full().pr(editor_margins.right);
3331
3332 let jump_data = header_jump_data(snapshot, block_row_start, *height, first_excerpt);
3333 result
3334 .child(self.render_buffer_header(
3335 first_excerpt,
3336 true,
3337 selected,
3338 false,
3339 jump_data,
3340 window,
3341 cx,
3342 ))
3343 .into_any_element()
3344 }
3345
3346 Block::ExcerptBoundary {
3347 excerpt,
3348 height,
3349 starts_new_buffer,
3350 ..
3351 } => {
3352 let color = cx.theme().colors().clone();
3353 let mut result = v_flex().id(block_id).w_full();
3354
3355 let jump_data = header_jump_data(snapshot, block_row_start, *height, excerpt);
3356
3357 if *starts_new_buffer {
3358 if sticky_header_excerpt_id != Some(excerpt.id) {
3359 let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
3360
3361 result = result.child(div().pr(editor_margins.right).child(
3362 self.render_buffer_header(
3363 excerpt, false, selected, false, jump_data, window, cx,
3364 ),
3365 ));
3366 } else {
3367 result =
3368 result.child(div().h(FILE_HEADER_HEIGHT as f32 * window.line_height()));
3369 }
3370 } else {
3371 result = result.child(
3372 h_flex().relative().child(
3373 div()
3374 .top(line_height / 2.)
3375 .absolute()
3376 .w_full()
3377 .h_px()
3378 .bg(color.border_variant),
3379 ),
3380 );
3381 };
3382
3383 result.into_any()
3384 }
3385 };
3386
3387 // Discover the element's content height, then round up to the nearest multiple of line height.
3388 let preliminary_size = element.layout_as_root(
3389 size(available_width, AvailableSpace::MinContent),
3390 window,
3391 cx,
3392 );
3393 let quantized_height = (preliminary_size.height / line_height).ceil() * line_height;
3394 let final_size = if preliminary_size.height == quantized_height {
3395 preliminary_size
3396 } else {
3397 element.layout_as_root(size(available_width, quantized_height.into()), window, cx)
3398 };
3399 let mut element_height_in_lines = ((final_size.height / line_height).ceil() as u32).max(1);
3400
3401 let mut row = block_row_start;
3402 let mut x_offset = px(0.);
3403 let mut is_block = true;
3404
3405 if let BlockId::Custom(custom_block_id) = block_id {
3406 if block.has_height() {
3407 if block.place_near() {
3408 if let Some((x_target, line_width)) = x_position {
3409 let margin = em_width * 2;
3410 if line_width + final_size.width + margin
3411 < editor_width + editor_margins.gutter.full_width()
3412 && !row_block_types.contains_key(&(row - 1))
3413 && element_height_in_lines == 1
3414 {
3415 x_offset = line_width + margin;
3416 row = row - 1;
3417 is_block = false;
3418 element_height_in_lines = 0;
3419 row_block_types.insert(row, is_block);
3420 } else {
3421 let max_offset = editor_width + editor_margins.gutter.full_width()
3422 - final_size.width;
3423 let min_offset = (x_target + em_width - final_size.width)
3424 .max(editor_margins.gutter.full_width());
3425 x_offset = x_target.min(max_offset).max(min_offset);
3426 }
3427 }
3428 };
3429 if element_height_in_lines != block.height() {
3430 resized_blocks.insert(custom_block_id, element_height_in_lines);
3431 }
3432 }
3433 }
3434 for i in 0..element_height_in_lines {
3435 row_block_types.insert(row + i, is_block);
3436 }
3437
3438 Some((element, final_size, row, x_offset))
3439 }
3440
3441 fn render_buffer_header(
3442 &self,
3443 for_excerpt: &ExcerptInfo,
3444 is_folded: bool,
3445 is_selected: bool,
3446 is_sticky: bool,
3447 jump_data: JumpData,
3448 window: &mut Window,
3449 cx: &mut App,
3450 ) -> Div {
3451 let editor = self.editor.read(cx);
3452 let file_status = editor
3453 .buffer
3454 .read(cx)
3455 .all_diff_hunks_expanded()
3456 .then(|| {
3457 editor
3458 .project
3459 .as_ref()?
3460 .read(cx)
3461 .status_for_buffer_id(for_excerpt.buffer_id, cx)
3462 })
3463 .flatten();
3464
3465 let include_root = editor
3466 .project
3467 .as_ref()
3468 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
3469 .unwrap_or_default();
3470 let can_open_excerpts = Editor::can_open_excerpts_in_file(for_excerpt.buffer.file());
3471 let path = for_excerpt.buffer.resolve_file_path(cx, include_root);
3472 let filename = path
3473 .as_ref()
3474 .and_then(|path| Some(path.file_name()?.to_string_lossy().to_string()));
3475 let parent_path = path.as_ref().and_then(|path| {
3476 Some(path.parent()?.to_string_lossy().to_string() + std::path::MAIN_SEPARATOR_STR)
3477 });
3478 let focus_handle = editor.focus_handle(cx);
3479 let colors = cx.theme().colors();
3480
3481 div()
3482 .p_1()
3483 .w_full()
3484 .h(FILE_HEADER_HEIGHT as f32 * window.line_height())
3485 .child(
3486 h_flex()
3487 .size_full()
3488 .gap_2()
3489 .flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
3490 .pl_0p5()
3491 .pr_5()
3492 .rounded_sm()
3493 .when(is_sticky, |el| el.shadow_md())
3494 .border_1()
3495 .map(|div| {
3496 let border_color = if is_selected
3497 && is_folded
3498 && focus_handle.contains_focused(window, cx)
3499 {
3500 colors.border_focused
3501 } else {
3502 colors.border
3503 };
3504 div.border_color(border_color)
3505 })
3506 .bg(colors.editor_subheader_background)
3507 .hover(|style| style.bg(colors.element_hover))
3508 .map(|header| {
3509 let editor = self.editor.clone();
3510 let buffer_id = for_excerpt.buffer_id;
3511 let toggle_chevron_icon =
3512 FileIcons::get_chevron_icon(!is_folded, cx).map(Icon::from_path);
3513 header.child(
3514 div()
3515 .hover(|style| style.bg(colors.element_selected))
3516 .rounded_xs()
3517 .child(
3518 ButtonLike::new("toggle-buffer-fold")
3519 .style(ui::ButtonStyle::Transparent)
3520 .height(px(28.).into())
3521 .width(px(28.).into())
3522 .children(toggle_chevron_icon)
3523 .tooltip({
3524 let focus_handle = focus_handle.clone();
3525 move |window, cx| {
3526 Tooltip::for_action_in(
3527 "Toggle Excerpt Fold",
3528 &ToggleFold,
3529 &focus_handle,
3530 window,
3531 cx,
3532 )
3533 }
3534 })
3535 .on_click(move |_, _, cx| {
3536 if is_folded {
3537 editor.update(cx, |editor, cx| {
3538 editor.unfold_buffer(buffer_id, cx);
3539 });
3540 } else {
3541 editor.update(cx, |editor, cx| {
3542 editor.fold_buffer(buffer_id, cx);
3543 });
3544 }
3545 }),
3546 ),
3547 )
3548 })
3549 .children(
3550 editor
3551 .addons
3552 .values()
3553 .filter_map(|addon| {
3554 addon.render_buffer_header_controls(for_excerpt, window, cx)
3555 })
3556 .take(1),
3557 )
3558 .child(
3559 h_flex()
3560 .cursor_pointer()
3561 .id("path header block")
3562 .size_full()
3563 .justify_between()
3564 .child(
3565 h_flex()
3566 .gap_2()
3567 .child(
3568 Label::new(
3569 filename
3570 .map(SharedString::from)
3571 .unwrap_or_else(|| "untitled".into()),
3572 )
3573 .single_line()
3574 .when_some(
3575 file_status,
3576 |el, status| {
3577 el.color(if status.is_conflicted() {
3578 Color::Conflict
3579 } else if status.is_modified() {
3580 Color::Modified
3581 } else if status.is_deleted() {
3582 Color::Disabled
3583 } else {
3584 Color::Created
3585 })
3586 .when(status.is_deleted(), |el| el.strikethrough())
3587 },
3588 ),
3589 )
3590 .when_some(parent_path, |then, path| {
3591 then.child(div().child(path).text_color(
3592 if file_status.is_some_and(FileStatus::is_deleted) {
3593 colors.text_disabled
3594 } else {
3595 colors.text_muted
3596 },
3597 ))
3598 }),
3599 )
3600 .when(can_open_excerpts && is_selected && path.is_some(), |el| {
3601 el.child(
3602 h_flex()
3603 .id("jump-to-file-button")
3604 .gap_2p5()
3605 .child(Label::new("Jump To File"))
3606 .children(
3607 KeyBinding::for_action_in(
3608 &OpenExcerpts,
3609 &focus_handle,
3610 window,
3611 cx,
3612 )
3613 .map(|binding| binding.into_any_element()),
3614 ),
3615 )
3616 })
3617 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
3618 .on_click(window.listener_for(&self.editor, {
3619 move |editor, e: &ClickEvent, window, cx| {
3620 editor.open_excerpts_common(
3621 Some(jump_data.clone()),
3622 e.down.modifiers.secondary(),
3623 window,
3624 cx,
3625 );
3626 }
3627 })),
3628 ),
3629 )
3630 }
3631
3632 fn render_blocks(
3633 &self,
3634 rows: Range<DisplayRow>,
3635 snapshot: &EditorSnapshot,
3636 hitbox: &Hitbox,
3637 text_hitbox: &Hitbox,
3638 editor_width: Pixels,
3639 scroll_width: &mut Pixels,
3640 editor_margins: &EditorMargins,
3641 em_width: Pixels,
3642 text_x: Pixels,
3643 line_height: Pixels,
3644 line_layouts: &mut [LineWithInvisibles],
3645 selections: &[Selection<Point>],
3646 selected_buffer_ids: &Vec<BufferId>,
3647 is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
3648 sticky_header_excerpt_id: Option<ExcerptId>,
3649 window: &mut Window,
3650 cx: &mut App,
3651 ) -> Result<(Vec<BlockLayout>, HashMap<DisplayRow, bool>), HashMap<CustomBlockId, u32>> {
3652 let (fixed_blocks, non_fixed_blocks) = snapshot
3653 .blocks_in_range(rows.clone())
3654 .partition::<Vec<_>, _>(|(_, block)| block.style() == BlockStyle::Fixed);
3655
3656 let mut focused_block = self
3657 .editor
3658 .update(cx, |editor, _| editor.take_focused_block());
3659 let mut fixed_block_max_width = Pixels::ZERO;
3660 let mut blocks = Vec::new();
3661 let mut resized_blocks = HashMap::default();
3662 let mut row_block_types = HashMap::default();
3663
3664 for (row, block) in fixed_blocks {
3665 let block_id = block.id();
3666
3667 if focused_block.as_ref().map_or(false, |b| b.id == block_id) {
3668 focused_block = None;
3669 }
3670
3671 if let Some((element, element_size, row, x_offset)) = self.render_block(
3672 block,
3673 AvailableSpace::MinContent,
3674 block_id,
3675 row,
3676 snapshot,
3677 text_x,
3678 &rows,
3679 line_layouts,
3680 editor_margins,
3681 line_height,
3682 em_width,
3683 text_hitbox,
3684 editor_width,
3685 scroll_width,
3686 &mut resized_blocks,
3687 &mut row_block_types,
3688 selections,
3689 selected_buffer_ids,
3690 is_row_soft_wrapped,
3691 sticky_header_excerpt_id,
3692 window,
3693 cx,
3694 ) {
3695 fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
3696 blocks.push(BlockLayout {
3697 id: block_id,
3698 x_offset,
3699 row: Some(row),
3700 element,
3701 available_space: size(AvailableSpace::MinContent, element_size.height.into()),
3702 style: BlockStyle::Fixed,
3703 overlaps_gutter: true,
3704 is_buffer_header: block.is_buffer_header(),
3705 });
3706 }
3707 }
3708
3709 for (row, block) in non_fixed_blocks {
3710 let style = block.style();
3711 let width = match (style, block.place_near()) {
3712 (_, true) => AvailableSpace::MinContent,
3713 (BlockStyle::Sticky, _) => hitbox.size.width.into(),
3714 (BlockStyle::Flex, _) => hitbox
3715 .size
3716 .width
3717 .max(fixed_block_max_width)
3718 .max(editor_margins.gutter.width + *scroll_width)
3719 .into(),
3720 (BlockStyle::Fixed, _) => unreachable!(),
3721 };
3722 let block_id = block.id();
3723
3724 if focused_block.as_ref().map_or(false, |b| b.id == block_id) {
3725 focused_block = None;
3726 }
3727
3728 if let Some((element, element_size, row, x_offset)) = self.render_block(
3729 block,
3730 width,
3731 block_id,
3732 row,
3733 snapshot,
3734 text_x,
3735 &rows,
3736 line_layouts,
3737 editor_margins,
3738 line_height,
3739 em_width,
3740 text_hitbox,
3741 editor_width,
3742 scroll_width,
3743 &mut resized_blocks,
3744 &mut row_block_types,
3745 selections,
3746 selected_buffer_ids,
3747 is_row_soft_wrapped,
3748 sticky_header_excerpt_id,
3749 window,
3750 cx,
3751 ) {
3752 blocks.push(BlockLayout {
3753 id: block_id,
3754 x_offset,
3755 row: Some(row),
3756 element,
3757 available_space: size(width, element_size.height.into()),
3758 style,
3759 overlaps_gutter: !block.place_near(),
3760 is_buffer_header: block.is_buffer_header(),
3761 });
3762 }
3763 }
3764
3765 if let Some(focused_block) = focused_block {
3766 if let Some(focus_handle) = focused_block.focus_handle.upgrade() {
3767 if focus_handle.is_focused(window) {
3768 if let Some(block) = snapshot.block_for_id(focused_block.id) {
3769 let style = block.style();
3770 let width = match style {
3771 BlockStyle::Fixed => AvailableSpace::MinContent,
3772 BlockStyle::Flex => AvailableSpace::Definite(
3773 hitbox
3774 .size
3775 .width
3776 .max(fixed_block_max_width)
3777 .max(editor_margins.gutter.width + *scroll_width),
3778 ),
3779 BlockStyle::Sticky => AvailableSpace::Definite(hitbox.size.width),
3780 };
3781
3782 if let Some((element, element_size, _, x_offset)) = self.render_block(
3783 &block,
3784 width,
3785 focused_block.id,
3786 rows.end,
3787 snapshot,
3788 text_x,
3789 &rows,
3790 line_layouts,
3791 editor_margins,
3792 line_height,
3793 em_width,
3794 text_hitbox,
3795 editor_width,
3796 scroll_width,
3797 &mut resized_blocks,
3798 &mut row_block_types,
3799 selections,
3800 selected_buffer_ids,
3801 is_row_soft_wrapped,
3802 sticky_header_excerpt_id,
3803 window,
3804 cx,
3805 ) {
3806 blocks.push(BlockLayout {
3807 id: block.id(),
3808 x_offset,
3809 row: None,
3810 element,
3811 available_space: size(width, element_size.height.into()),
3812 style,
3813 overlaps_gutter: true,
3814 is_buffer_header: block.is_buffer_header(),
3815 });
3816 }
3817 }
3818 }
3819 }
3820 }
3821
3822 if resized_blocks.is_empty() {
3823 *scroll_width =
3824 (*scroll_width).max(fixed_block_max_width - editor_margins.gutter.width);
3825 Ok((blocks, row_block_types))
3826 } else {
3827 Err(resized_blocks)
3828 }
3829 }
3830
3831 fn layout_blocks(
3832 &self,
3833 blocks: &mut Vec<BlockLayout>,
3834 hitbox: &Hitbox,
3835 line_height: Pixels,
3836 scroll_pixel_position: gpui::Point<Pixels>,
3837 window: &mut Window,
3838 cx: &mut App,
3839 ) {
3840 for block in blocks {
3841 let mut origin = if let Some(row) = block.row {
3842 hitbox.origin
3843 + point(
3844 block.x_offset,
3845 row.as_f32() * line_height - scroll_pixel_position.y,
3846 )
3847 } else {
3848 // Position the block outside the visible area
3849 hitbox.origin + point(Pixels::ZERO, hitbox.size.height)
3850 };
3851
3852 if !matches!(block.style, BlockStyle::Sticky) {
3853 origin += point(-scroll_pixel_position.x, Pixels::ZERO);
3854 }
3855
3856 let focus_handle =
3857 block
3858 .element
3859 .prepaint_as_root(origin, block.available_space, window, cx);
3860
3861 if let Some(focus_handle) = focus_handle {
3862 self.editor.update(cx, |editor, _cx| {
3863 editor.set_focused_block(FocusedBlock {
3864 id: block.id,
3865 focus_handle: focus_handle.downgrade(),
3866 });
3867 });
3868 }
3869 }
3870 }
3871
3872 fn layout_sticky_buffer_header(
3873 &self,
3874 StickyHeaderExcerpt { excerpt }: StickyHeaderExcerpt<'_>,
3875 scroll_position: f32,
3876 line_height: Pixels,
3877 right_margin: Pixels,
3878 snapshot: &EditorSnapshot,
3879 hitbox: &Hitbox,
3880 selected_buffer_ids: &Vec<BufferId>,
3881 blocks: &[BlockLayout],
3882 window: &mut Window,
3883 cx: &mut App,
3884 ) -> AnyElement {
3885 let jump_data = header_jump_data(
3886 snapshot,
3887 DisplayRow(scroll_position as u32),
3888 FILE_HEADER_HEIGHT + MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3889 excerpt,
3890 );
3891
3892 let editor_bg_color = cx.theme().colors().editor_background;
3893
3894 let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
3895
3896 let available_width = hitbox.bounds.size.width - right_margin;
3897
3898 let mut header = v_flex()
3899 .relative()
3900 .child(
3901 div()
3902 .w(available_width)
3903 .h(FILE_HEADER_HEIGHT as f32 * line_height)
3904 .bg(linear_gradient(
3905 0.,
3906 linear_color_stop(editor_bg_color.opacity(0.), 0.),
3907 linear_color_stop(editor_bg_color, 0.6),
3908 ))
3909 .absolute()
3910 .top_0(),
3911 )
3912 .child(
3913 self.render_buffer_header(excerpt, false, selected, true, jump_data, window, cx)
3914 .into_any_element(),
3915 )
3916 .into_any_element();
3917
3918 let mut origin = hitbox.origin;
3919 // Move floating header up to avoid colliding with the next buffer header.
3920 for block in blocks.iter() {
3921 if !block.is_buffer_header {
3922 continue;
3923 }
3924
3925 let Some(display_row) = block.row.filter(|row| row.0 > scroll_position as u32) else {
3926 continue;
3927 };
3928
3929 let max_row = display_row.0.saturating_sub(FILE_HEADER_HEIGHT);
3930 let offset = scroll_position - max_row as f32;
3931
3932 if offset > 0.0 {
3933 origin.y -= offset * line_height;
3934 }
3935 break;
3936 }
3937
3938 let size = size(
3939 AvailableSpace::Definite(available_width),
3940 AvailableSpace::MinContent,
3941 );
3942
3943 header.prepaint_as_root(origin, size, window, cx);
3944
3945 header
3946 }
3947
3948 fn layout_cursor_popovers(
3949 &self,
3950 line_height: Pixels,
3951 text_hitbox: &Hitbox,
3952 content_origin: gpui::Point<Pixels>,
3953 right_margin: Pixels,
3954 start_row: DisplayRow,
3955 scroll_pixel_position: gpui::Point<Pixels>,
3956 line_layouts: &[LineWithInvisibles],
3957 cursor: DisplayPoint,
3958 cursor_point: Point,
3959 style: &EditorStyle,
3960 window: &mut Window,
3961 cx: &mut App,
3962 ) -> Option<ContextMenuLayout> {
3963 let mut min_menu_height = Pixels::ZERO;
3964 let mut max_menu_height = Pixels::ZERO;
3965 let mut height_above_menu = Pixels::ZERO;
3966 let height_below_menu = Pixels::ZERO;
3967 let mut edit_prediction_popover_visible = false;
3968 let mut context_menu_visible = false;
3969 let context_menu_placement;
3970
3971 {
3972 let editor = self.editor.read(cx);
3973 if editor
3974 .edit_prediction_visible_in_cursor_popover(editor.has_active_inline_completion())
3975 {
3976 height_above_menu +=
3977 editor.edit_prediction_cursor_popover_height() + POPOVER_Y_PADDING;
3978 edit_prediction_popover_visible = true;
3979 }
3980
3981 if editor.context_menu_visible() {
3982 if let Some(crate::ContextMenuOrigin::Cursor) = editor.context_menu_origin() {
3983 let (min_height_in_lines, max_height_in_lines) = editor
3984 .context_menu_options
3985 .as_ref()
3986 .map_or((3, 12), |options| {
3987 (options.min_entries_visible, options.max_entries_visible)
3988 });
3989
3990 min_menu_height += line_height * min_height_in_lines as f32 + POPOVER_Y_PADDING;
3991 max_menu_height += line_height * max_height_in_lines as f32 + POPOVER_Y_PADDING;
3992 context_menu_visible = true;
3993 }
3994 }
3995 context_menu_placement = editor
3996 .context_menu_options
3997 .as_ref()
3998 .and_then(|options| options.placement.clone());
3999 }
4000
4001 let visible = edit_prediction_popover_visible || context_menu_visible;
4002 if !visible {
4003 return None;
4004 }
4005
4006 let cursor_row_layout = &line_layouts[cursor.row().minus(start_row) as usize];
4007 let target_position = content_origin
4008 + gpui::Point {
4009 x: cmp::max(
4010 px(0.),
4011 cursor_row_layout.x_for_index(cursor.column() as usize)
4012 - scroll_pixel_position.x,
4013 ),
4014 y: cmp::max(
4015 px(0.),
4016 cursor.row().next_row().as_f32() * line_height - scroll_pixel_position.y,
4017 ),
4018 };
4019
4020 let viewport_bounds =
4021 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
4022 right: -right_margin - MENU_GAP,
4023 ..Default::default()
4024 });
4025
4026 let min_height = height_above_menu + min_menu_height + height_below_menu;
4027 let max_height = height_above_menu + max_menu_height + height_below_menu;
4028 let (laid_out_popovers, y_flipped) = self.layout_popovers_above_or_below_line(
4029 target_position,
4030 line_height,
4031 min_height,
4032 max_height,
4033 context_menu_placement,
4034 text_hitbox,
4035 viewport_bounds,
4036 window,
4037 cx,
4038 |height, max_width_for_stable_x, y_flipped, window, cx| {
4039 // First layout the menu to get its size - others can be at least this wide.
4040 let context_menu = if context_menu_visible {
4041 let menu_height = if y_flipped {
4042 height - height_below_menu
4043 } else {
4044 height - height_above_menu
4045 };
4046 let mut element = self
4047 .render_context_menu(line_height, menu_height, window, cx)
4048 .expect("Visible context menu should always render.");
4049 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
4050 Some((CursorPopoverType::CodeContextMenu, element, size))
4051 } else {
4052 None
4053 };
4054 let min_width = context_menu
4055 .as_ref()
4056 .map_or(px(0.), |(_, _, size)| size.width);
4057 let max_width = max_width_for_stable_x.max(
4058 context_menu
4059 .as_ref()
4060 .map_or(px(0.), |(_, _, size)| size.width),
4061 );
4062
4063 let edit_prediction = if edit_prediction_popover_visible {
4064 self.editor.update(cx, move |editor, cx| {
4065 let accept_binding =
4066 editor.accept_edit_prediction_keybind(false, window, cx);
4067 let mut element = editor.render_edit_prediction_cursor_popover(
4068 min_width,
4069 max_width,
4070 cursor_point,
4071 style,
4072 accept_binding.keystroke(),
4073 window,
4074 cx,
4075 )?;
4076 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
4077 Some((CursorPopoverType::EditPrediction, element, size))
4078 })
4079 } else {
4080 None
4081 };
4082 vec![edit_prediction, context_menu]
4083 .into_iter()
4084 .flatten()
4085 .collect::<Vec<_>>()
4086 },
4087 )?;
4088
4089 let (menu_ix, (_, menu_bounds)) = laid_out_popovers
4090 .iter()
4091 .find_position(|(x, _)| matches!(x, CursorPopoverType::CodeContextMenu))?;
4092 let last_ix = laid_out_popovers.len() - 1;
4093 let menu_is_last = menu_ix == last_ix;
4094 let first_popover_bounds = laid_out_popovers[0].1;
4095 let last_popover_bounds = laid_out_popovers[last_ix].1;
4096
4097 // Bounds to layout the aside around. When y_flipped, the aside goes either above or to the
4098 // right, and otherwise it goes below or to the right.
4099 let mut target_bounds = Bounds::from_corners(
4100 first_popover_bounds.origin,
4101 last_popover_bounds.bottom_right(),
4102 );
4103 target_bounds.size.width = menu_bounds.size.width;
4104
4105 // Like `target_bounds`, but with the max height it could occupy. Choosing an aside position
4106 // based on this is preferred for layout stability.
4107 let mut max_target_bounds = target_bounds;
4108 max_target_bounds.size.height = max_height;
4109 if y_flipped {
4110 max_target_bounds.origin.y -= max_height - target_bounds.size.height;
4111 }
4112
4113 // Add spacing around `target_bounds` and `max_target_bounds`.
4114 let mut extend_amount = Edges::all(MENU_GAP);
4115 if y_flipped {
4116 extend_amount.bottom = line_height;
4117 } else {
4118 extend_amount.top = line_height;
4119 }
4120 let target_bounds = target_bounds.extend(extend_amount);
4121 let max_target_bounds = max_target_bounds.extend(extend_amount);
4122
4123 let must_place_above_or_below =
4124 if y_flipped && !menu_is_last && menu_bounds.size.height < max_menu_height {
4125 laid_out_popovers[menu_ix + 1..]
4126 .iter()
4127 .any(|(_, popover_bounds)| popover_bounds.size.width > menu_bounds.size.width)
4128 } else {
4129 false
4130 };
4131
4132 let aside_bounds = self.layout_context_menu_aside(
4133 y_flipped,
4134 *menu_bounds,
4135 target_bounds,
4136 max_target_bounds,
4137 max_menu_height,
4138 must_place_above_or_below,
4139 text_hitbox,
4140 viewport_bounds,
4141 window,
4142 cx,
4143 );
4144
4145 if let Some(menu_bounds) = laid_out_popovers.iter().find_map(|(popover_type, bounds)| {
4146 if matches!(popover_type, CursorPopoverType::CodeContextMenu) {
4147 Some(*bounds)
4148 } else {
4149 None
4150 }
4151 }) {
4152 let bounds = if let Some(aside_bounds) = aside_bounds {
4153 menu_bounds.union(&aside_bounds)
4154 } else {
4155 menu_bounds
4156 };
4157 return Some(ContextMenuLayout { y_flipped, bounds });
4158 }
4159
4160 None
4161 }
4162
4163 fn layout_gutter_menu(
4164 &self,
4165 line_height: Pixels,
4166 text_hitbox: &Hitbox,
4167 content_origin: gpui::Point<Pixels>,
4168 right_margin: Pixels,
4169 scroll_pixel_position: gpui::Point<Pixels>,
4170 gutter_overshoot: Pixels,
4171 window: &mut Window,
4172 cx: &mut App,
4173 ) {
4174 let editor = self.editor.read(cx);
4175 if !editor.context_menu_visible() {
4176 return;
4177 }
4178 let Some(crate::ContextMenuOrigin::GutterIndicator(gutter_row)) =
4179 editor.context_menu_origin()
4180 else {
4181 return;
4182 };
4183 // Context menu was spawned via a click on a gutter. Ensure it's a bit closer to the
4184 // indicator than just a plain first column of the text field.
4185 let target_position = content_origin
4186 + gpui::Point {
4187 x: -gutter_overshoot,
4188 y: gutter_row.next_row().as_f32() * line_height - scroll_pixel_position.y,
4189 };
4190
4191 let (min_height_in_lines, max_height_in_lines) = editor
4192 .context_menu_options
4193 .as_ref()
4194 .map_or((3, 12), |options| {
4195 (options.min_entries_visible, options.max_entries_visible)
4196 });
4197
4198 let min_height = line_height * min_height_in_lines as f32 + POPOVER_Y_PADDING;
4199 let max_height = line_height * max_height_in_lines as f32 + POPOVER_Y_PADDING;
4200 let viewport_bounds =
4201 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
4202 right: -right_margin - MENU_GAP,
4203 ..Default::default()
4204 });
4205 self.layout_popovers_above_or_below_line(
4206 target_position,
4207 line_height,
4208 min_height,
4209 max_height,
4210 editor
4211 .context_menu_options
4212 .as_ref()
4213 .and_then(|options| options.placement.clone()),
4214 text_hitbox,
4215 viewport_bounds,
4216 window,
4217 cx,
4218 move |height, _max_width_for_stable_x, _, window, cx| {
4219 let mut element = self
4220 .render_context_menu(line_height, height, window, cx)
4221 .expect("Visible context menu should always render.");
4222 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
4223 vec![(CursorPopoverType::CodeContextMenu, element, size)]
4224 },
4225 );
4226 }
4227
4228 fn layout_popovers_above_or_below_line(
4229 &self,
4230 target_position: gpui::Point<Pixels>,
4231 line_height: Pixels,
4232 min_height: Pixels,
4233 max_height: Pixels,
4234 placement: Option<ContextMenuPlacement>,
4235 text_hitbox: &Hitbox,
4236 viewport_bounds: Bounds<Pixels>,
4237 window: &mut Window,
4238 cx: &mut App,
4239 make_sized_popovers: impl FnOnce(
4240 Pixels,
4241 Pixels,
4242 bool,
4243 &mut Window,
4244 &mut App,
4245 ) -> Vec<(CursorPopoverType, AnyElement, Size<Pixels>)>,
4246 ) -> Option<(Vec<(CursorPopoverType, Bounds<Pixels>)>, bool)> {
4247 let text_style = TextStyleRefinement {
4248 line_height: Some(DefiniteLength::Fraction(
4249 BufferLineHeight::Comfortable.value(),
4250 )),
4251 ..Default::default()
4252 };
4253 window.with_text_style(Some(text_style), |window| {
4254 // If the max height won't fit below and there is more space above, put it above the line.
4255 let bottom_y_when_flipped = target_position.y - line_height;
4256 let available_above = bottom_y_when_flipped - text_hitbox.top();
4257 let available_below = text_hitbox.bottom() - target_position.y;
4258 let y_overflows_below = max_height > available_below;
4259 let mut y_flipped = match placement {
4260 Some(ContextMenuPlacement::Above) => true,
4261 Some(ContextMenuPlacement::Below) => false,
4262 None => y_overflows_below && available_above > available_below,
4263 };
4264 let mut height = cmp::min(
4265 max_height,
4266 if y_flipped {
4267 available_above
4268 } else {
4269 available_below
4270 },
4271 );
4272
4273 // If the min height doesn't fit within text bounds, instead fit within the window.
4274 if height < min_height {
4275 let available_above = bottom_y_when_flipped;
4276 let available_below = viewport_bounds.bottom() - target_position.y;
4277 let (y_flipped_override, height_override) = match placement {
4278 Some(ContextMenuPlacement::Above) => {
4279 (true, cmp::min(available_above, min_height))
4280 }
4281 Some(ContextMenuPlacement::Below) => {
4282 (false, cmp::min(available_below, min_height))
4283 }
4284 None => {
4285 if available_below > min_height {
4286 (false, min_height)
4287 } else if available_above > min_height {
4288 (true, min_height)
4289 } else if available_above > available_below {
4290 (true, available_above)
4291 } else {
4292 (false, available_below)
4293 }
4294 }
4295 };
4296 y_flipped = y_flipped_override;
4297 height = height_override;
4298 }
4299
4300 let max_width_for_stable_x = viewport_bounds.right() - target_position.x;
4301
4302 // TODO: Use viewport_bounds.width as a max width so that it doesn't get clipped on the left
4303 // for very narrow windows.
4304 let popovers =
4305 make_sized_popovers(height, max_width_for_stable_x, y_flipped, window, cx);
4306 if popovers.is_empty() {
4307 return None;
4308 }
4309
4310 let max_width = popovers
4311 .iter()
4312 .map(|(_, _, size)| size.width)
4313 .max()
4314 .unwrap_or_default();
4315
4316 let mut current_position = gpui::Point {
4317 // Snap the right edge of the list to the right edge of the window if its horizontal bounds
4318 // overflow. Include space for the scrollbar.
4319 x: target_position
4320 .x
4321 .min((viewport_bounds.right() - max_width).max(Pixels::ZERO)),
4322 y: if y_flipped {
4323 bottom_y_when_flipped
4324 } else {
4325 target_position.y
4326 },
4327 };
4328
4329 let mut laid_out_popovers = popovers
4330 .into_iter()
4331 .map(|(popover_type, element, size)| {
4332 if y_flipped {
4333 current_position.y -= size.height;
4334 }
4335 let position = current_position;
4336 window.defer_draw(element, current_position, 1);
4337 if !y_flipped {
4338 current_position.y += size.height + MENU_GAP;
4339 } else {
4340 current_position.y -= MENU_GAP;
4341 }
4342 (popover_type, Bounds::new(position, size))
4343 })
4344 .collect::<Vec<_>>();
4345
4346 if y_flipped {
4347 laid_out_popovers.reverse();
4348 }
4349
4350 Some((laid_out_popovers, y_flipped))
4351 })
4352 }
4353
4354 fn layout_context_menu_aside(
4355 &self,
4356 y_flipped: bool,
4357 menu_bounds: Bounds<Pixels>,
4358 target_bounds: Bounds<Pixels>,
4359 max_target_bounds: Bounds<Pixels>,
4360 max_height: Pixels,
4361 must_place_above_or_below: bool,
4362 text_hitbox: &Hitbox,
4363 viewport_bounds: Bounds<Pixels>,
4364 window: &mut Window,
4365 cx: &mut App,
4366 ) -> Option<Bounds<Pixels>> {
4367 let available_within_viewport = target_bounds.space_within(&viewport_bounds);
4368 let positioned_aside = if available_within_viewport.right >= MENU_ASIDE_MIN_WIDTH
4369 && !must_place_above_or_below
4370 {
4371 let max_width = cmp::min(
4372 available_within_viewport.right - px(1.),
4373 MENU_ASIDE_MAX_WIDTH,
4374 );
4375 let mut aside = self.render_context_menu_aside(
4376 size(max_width, max_height - POPOVER_Y_PADDING),
4377 window,
4378 cx,
4379 )?;
4380 let size = aside.layout_as_root(AvailableSpace::min_size(), window, cx);
4381 let right_position = point(target_bounds.right(), menu_bounds.origin.y);
4382 Some((aside, right_position, size))
4383 } else {
4384 let max_size = size(
4385 // TODO(mgsloan): Once the menu is bounded by viewport width the bound on viewport
4386 // won't be needed here.
4387 cmp::min(
4388 cmp::max(menu_bounds.size.width - px(2.), MENU_ASIDE_MIN_WIDTH),
4389 viewport_bounds.right(),
4390 ),
4391 cmp::min(
4392 max_height,
4393 cmp::max(
4394 available_within_viewport.top,
4395 available_within_viewport.bottom,
4396 ),
4397 ) - POPOVER_Y_PADDING,
4398 );
4399 let mut aside = self.render_context_menu_aside(max_size, window, cx)?;
4400 let actual_size = aside.layout_as_root(AvailableSpace::min_size(), window, cx);
4401
4402 let top_position = point(
4403 menu_bounds.origin.x,
4404 target_bounds.top() - actual_size.height,
4405 );
4406 let bottom_position = point(menu_bounds.origin.x, target_bounds.bottom());
4407
4408 let fit_within = |available: Edges<Pixels>, wanted: Size<Pixels>| {
4409 // Prefer to fit on the same side of the line as the menu, then on the other side of
4410 // the line.
4411 if !y_flipped && wanted.height < available.bottom {
4412 Some(bottom_position)
4413 } else if !y_flipped && wanted.height < available.top {
4414 Some(top_position)
4415 } else if y_flipped && wanted.height < available.top {
4416 Some(top_position)
4417 } else if y_flipped && wanted.height < available.bottom {
4418 Some(bottom_position)
4419 } else {
4420 None
4421 }
4422 };
4423
4424 // Prefer choosing a direction using max sizes rather than actual size for stability.
4425 let available_within_text = max_target_bounds.space_within(&text_hitbox.bounds);
4426 let wanted = size(MENU_ASIDE_MAX_WIDTH, max_height);
4427 let aside_position = fit_within(available_within_text, wanted)
4428 // Fallback: fit max size in window.
4429 .or_else(|| fit_within(max_target_bounds.space_within(&viewport_bounds), wanted))
4430 // Fallback: fit actual size in window.
4431 .or_else(|| fit_within(available_within_viewport, actual_size));
4432
4433 aside_position.map(|position| (aside, position, actual_size))
4434 };
4435
4436 // Skip drawing if it doesn't fit anywhere.
4437 if let Some((aside, position, size)) = positioned_aside {
4438 let aside_bounds = Bounds::new(position, size);
4439 window.defer_draw(aside, position, 2);
4440 return Some(aside_bounds);
4441 }
4442
4443 None
4444 }
4445
4446 fn render_context_menu(
4447 &self,
4448 line_height: Pixels,
4449 height: Pixels,
4450 window: &mut Window,
4451 cx: &mut App,
4452 ) -> Option<AnyElement> {
4453 let max_height_in_lines = ((height - POPOVER_Y_PADDING) / line_height).floor() as u32;
4454 self.editor.update(cx, |editor, cx| {
4455 editor.render_context_menu(&self.style, max_height_in_lines, window, cx)
4456 })
4457 }
4458
4459 fn render_context_menu_aside(
4460 &self,
4461 max_size: Size<Pixels>,
4462 window: &mut Window,
4463 cx: &mut App,
4464 ) -> Option<AnyElement> {
4465 if max_size.width < px(100.) || max_size.height < px(12.) {
4466 None
4467 } else {
4468 self.editor.update(cx, |editor, cx| {
4469 editor.render_context_menu_aside(max_size, window, cx)
4470 })
4471 }
4472 }
4473
4474 fn layout_mouse_context_menu(
4475 &self,
4476 editor_snapshot: &EditorSnapshot,
4477 visible_range: Range<DisplayRow>,
4478 content_origin: gpui::Point<Pixels>,
4479 window: &mut Window,
4480 cx: &mut App,
4481 ) -> Option<AnyElement> {
4482 let position = self.editor.update(cx, |editor, _cx| {
4483 let visible_start_point = editor.display_to_pixel_point(
4484 DisplayPoint::new(visible_range.start, 0),
4485 editor_snapshot,
4486 window,
4487 )?;
4488 let visible_end_point = editor.display_to_pixel_point(
4489 DisplayPoint::new(visible_range.end, 0),
4490 editor_snapshot,
4491 window,
4492 )?;
4493
4494 let mouse_context_menu = editor.mouse_context_menu.as_ref()?;
4495 let (source_display_point, position) = match mouse_context_menu.position {
4496 MenuPosition::PinnedToScreen(point) => (None, point),
4497 MenuPosition::PinnedToEditor { source, offset } => {
4498 let source_display_point = source.to_display_point(editor_snapshot);
4499 let source_point = editor.to_pixel_point(source, editor_snapshot, window)?;
4500 let position = content_origin + source_point + offset;
4501 (Some(source_display_point), position)
4502 }
4503 };
4504
4505 let source_included = source_display_point.map_or(true, |source_display_point| {
4506 visible_range
4507 .to_inclusive()
4508 .contains(&source_display_point.row())
4509 });
4510 let position_included =
4511 visible_start_point.y <= position.y && position.y <= visible_end_point.y;
4512 if !source_included && !position_included {
4513 None
4514 } else {
4515 Some(position)
4516 }
4517 })?;
4518
4519 let text_style = TextStyleRefinement {
4520 line_height: Some(DefiniteLength::Fraction(
4521 BufferLineHeight::Comfortable.value(),
4522 )),
4523 ..Default::default()
4524 };
4525 window.with_text_style(Some(text_style), |window| {
4526 let mut element = self.editor.read_with(cx, |editor, _| {
4527 let mouse_context_menu = editor.mouse_context_menu.as_ref()?;
4528 let context_menu = mouse_context_menu.context_menu.clone();
4529
4530 Some(
4531 deferred(
4532 anchored()
4533 .position(position)
4534 .child(context_menu)
4535 .anchor(Corner::TopLeft)
4536 .snap_to_window_with_margin(px(8.)),
4537 )
4538 .with_priority(1)
4539 .into_any(),
4540 )
4541 })?;
4542
4543 element.prepaint_as_root(position, AvailableSpace::min_size(), window, cx);
4544 Some(element)
4545 })
4546 }
4547
4548 fn layout_hover_popovers(
4549 &self,
4550 snapshot: &EditorSnapshot,
4551 hitbox: &Hitbox,
4552 visible_display_row_range: Range<DisplayRow>,
4553 content_origin: gpui::Point<Pixels>,
4554 scroll_pixel_position: gpui::Point<Pixels>,
4555 line_layouts: &[LineWithInvisibles],
4556 line_height: Pixels,
4557 em_width: Pixels,
4558 context_menu_layout: Option<ContextMenuLayout>,
4559 window: &mut Window,
4560 cx: &mut App,
4561 ) {
4562 struct MeasuredHoverPopover {
4563 element: AnyElement,
4564 size: Size<Pixels>,
4565 horizontal_offset: Pixels,
4566 }
4567
4568 let max_size = size(
4569 (120. * em_width) // Default size
4570 .min(hitbox.size.width / 2.) // Shrink to half of the editor width
4571 .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
4572 (16. * line_height) // Default size
4573 .min(hitbox.size.height / 2.) // Shrink to half of the editor height
4574 .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
4575 );
4576
4577 let hover_popovers = self.editor.update(cx, |editor, cx| {
4578 editor.hover_state.render(
4579 snapshot,
4580 visible_display_row_range.clone(),
4581 max_size,
4582 window,
4583 cx,
4584 )
4585 });
4586 let Some((position, hover_popovers)) = hover_popovers else {
4587 return;
4588 };
4589
4590 // This is safe because we check on layout whether the required row is available
4591 let hovered_row_layout =
4592 &line_layouts[position.row().minus(visible_display_row_range.start) as usize];
4593
4594 // Compute Hovered Point
4595 let x =
4596 hovered_row_layout.x_for_index(position.column() as usize) - scroll_pixel_position.x;
4597 let y = position.row().as_f32() * line_height - scroll_pixel_position.y;
4598 let hovered_point = content_origin + point(x, y);
4599
4600 let mut overall_height = Pixels::ZERO;
4601 let mut measured_hover_popovers = Vec::new();
4602 for (position, mut hover_popover) in hover_popovers.into_iter().with_position() {
4603 let size = hover_popover.layout_as_root(AvailableSpace::min_size(), window, cx);
4604 let horizontal_offset =
4605 (hitbox.top_right().x - POPOVER_RIGHT_OFFSET - (hovered_point.x + size.width))
4606 .min(Pixels::ZERO);
4607 match position {
4608 itertools::Position::Middle | itertools::Position::Last => {
4609 overall_height += HOVER_POPOVER_GAP
4610 }
4611 _ => {}
4612 }
4613 overall_height += size.height;
4614 measured_hover_popovers.push(MeasuredHoverPopover {
4615 element: hover_popover,
4616 size,
4617 horizontal_offset,
4618 });
4619 }
4620
4621 fn draw_occluder(
4622 width: Pixels,
4623 origin: gpui::Point<Pixels>,
4624 window: &mut Window,
4625 cx: &mut App,
4626 ) {
4627 let mut occlusion = div()
4628 .size_full()
4629 .occlude()
4630 .on_mouse_move(|_, _, cx| cx.stop_propagation())
4631 .into_any_element();
4632 occlusion.layout_as_root(size(width, HOVER_POPOVER_GAP).into(), window, cx);
4633 window.defer_draw(occlusion, origin, 2);
4634 }
4635
4636 fn place_popovers_above(
4637 hovered_point: gpui::Point<Pixels>,
4638 measured_hover_popovers: Vec<MeasuredHoverPopover>,
4639 window: &mut Window,
4640 cx: &mut App,
4641 ) {
4642 let mut current_y = hovered_point.y;
4643 for (position, popover) in measured_hover_popovers.into_iter().with_position() {
4644 let size = popover.size;
4645 let popover_origin = point(
4646 hovered_point.x + popover.horizontal_offset,
4647 current_y - size.height,
4648 );
4649
4650 window.defer_draw(popover.element, popover_origin, 2);
4651 if position != itertools::Position::Last {
4652 let origin = point(popover_origin.x, popover_origin.y - HOVER_POPOVER_GAP);
4653 draw_occluder(size.width, origin, window, cx);
4654 }
4655
4656 current_y = popover_origin.y - HOVER_POPOVER_GAP;
4657 }
4658 }
4659
4660 fn place_popovers_below(
4661 hovered_point: gpui::Point<Pixels>,
4662 measured_hover_popovers: Vec<MeasuredHoverPopover>,
4663 line_height: Pixels,
4664 window: &mut Window,
4665 cx: &mut App,
4666 ) {
4667 let mut current_y = hovered_point.y + line_height;
4668 for (position, popover) in measured_hover_popovers.into_iter().with_position() {
4669 let size = popover.size;
4670 let popover_origin = point(hovered_point.x + popover.horizontal_offset, current_y);
4671
4672 window.defer_draw(popover.element, popover_origin, 2);
4673 if position != itertools::Position::Last {
4674 let origin = point(popover_origin.x, popover_origin.y + size.height);
4675 draw_occluder(size.width, origin, window, cx);
4676 }
4677
4678 current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
4679 }
4680 }
4681
4682 let intersects_menu = |bounds: Bounds<Pixels>| -> bool {
4683 context_menu_layout
4684 .as_ref()
4685 .map_or(false, |menu| bounds.intersects(&menu.bounds))
4686 };
4687
4688 let can_place_above = {
4689 let mut bounds_above = Vec::new();
4690 let mut current_y = hovered_point.y;
4691 for popover in &measured_hover_popovers {
4692 let size = popover.size;
4693 let popover_origin = point(
4694 hovered_point.x + popover.horizontal_offset,
4695 current_y - size.height,
4696 );
4697 bounds_above.push(Bounds::new(popover_origin, size));
4698 current_y = popover_origin.y - HOVER_POPOVER_GAP;
4699 }
4700 bounds_above
4701 .iter()
4702 .all(|b| b.is_contained_within(hitbox) && !intersects_menu(*b))
4703 };
4704
4705 let can_place_below = || {
4706 let mut bounds_below = Vec::new();
4707 let mut current_y = hovered_point.y + line_height;
4708 for popover in &measured_hover_popovers {
4709 let size = popover.size;
4710 let popover_origin = point(hovered_point.x + popover.horizontal_offset, current_y);
4711 bounds_below.push(Bounds::new(popover_origin, size));
4712 current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
4713 }
4714 bounds_below
4715 .iter()
4716 .all(|b| b.is_contained_within(hitbox) && !intersects_menu(*b))
4717 };
4718
4719 if can_place_above {
4720 // try placing above hovered point
4721 place_popovers_above(hovered_point, measured_hover_popovers, window, cx);
4722 } else if can_place_below() {
4723 // try placing below hovered point
4724 place_popovers_below(
4725 hovered_point,
4726 measured_hover_popovers,
4727 line_height,
4728 window,
4729 cx,
4730 );
4731 } else {
4732 // try to place popovers around the context menu
4733 let origin_surrounding_menu = context_menu_layout.as_ref().and_then(|menu| {
4734 let total_width = measured_hover_popovers
4735 .iter()
4736 .map(|p| p.size.width)
4737 .max()
4738 .unwrap_or(Pixels::ZERO);
4739 let y_for_horizontal_positioning = if menu.y_flipped {
4740 menu.bounds.bottom() - overall_height
4741 } else {
4742 menu.bounds.top()
4743 };
4744 let possible_origins = vec![
4745 // left of context menu
4746 point(
4747 menu.bounds.left() - total_width - HOVER_POPOVER_GAP,
4748 y_for_horizontal_positioning,
4749 ),
4750 // right of context menu
4751 point(
4752 menu.bounds.right() + HOVER_POPOVER_GAP,
4753 y_for_horizontal_positioning,
4754 ),
4755 // top of context menu
4756 point(
4757 menu.bounds.left(),
4758 menu.bounds.top() - overall_height - HOVER_POPOVER_GAP,
4759 ),
4760 // bottom of context menu
4761 point(menu.bounds.left(), menu.bounds.bottom() + HOVER_POPOVER_GAP),
4762 ];
4763 possible_origins.into_iter().find(|&origin| {
4764 Bounds::new(origin, size(total_width, overall_height))
4765 .is_contained_within(hitbox)
4766 })
4767 });
4768 if let Some(origin) = origin_surrounding_menu {
4769 let mut current_y = origin.y;
4770 for (position, popover) in measured_hover_popovers.into_iter().with_position() {
4771 let size = popover.size;
4772 let popover_origin = point(origin.x, current_y);
4773
4774 window.defer_draw(popover.element, popover_origin, 2);
4775 if position != itertools::Position::Last {
4776 let origin = point(popover_origin.x, popover_origin.y + size.height);
4777 draw_occluder(size.width, origin, window, cx);
4778 }
4779
4780 current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
4781 }
4782 } else {
4783 // fallback to existing above/below cursor logic
4784 // this might overlap menu or overflow in rare case
4785 if can_place_above {
4786 place_popovers_above(hovered_point, measured_hover_popovers, window, cx);
4787 } else {
4788 place_popovers_below(
4789 hovered_point,
4790 measured_hover_popovers,
4791 line_height,
4792 window,
4793 cx,
4794 );
4795 }
4796 }
4797 }
4798 }
4799
4800 fn layout_diff_hunk_controls(
4801 &self,
4802 row_range: Range<DisplayRow>,
4803 row_infos: &[RowInfo],
4804 text_hitbox: &Hitbox,
4805 newest_cursor_position: Option<DisplayPoint>,
4806 line_height: Pixels,
4807 right_margin: Pixels,
4808 scroll_pixel_position: gpui::Point<Pixels>,
4809 display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
4810 highlighted_rows: &BTreeMap<DisplayRow, LineHighlight>,
4811 editor: Entity<Editor>,
4812 window: &mut Window,
4813 cx: &mut App,
4814 ) -> (Vec<AnyElement>, Vec<(DisplayRow, Bounds<Pixels>)>) {
4815 let render_diff_hunk_controls = editor.read(cx).render_diff_hunk_controls.clone();
4816 let hovered_diff_hunk_row = editor.read(cx).hovered_diff_hunk_row;
4817
4818 let mut controls = vec![];
4819 let mut control_bounds = vec![];
4820
4821 let active_positions = [
4822 hovered_diff_hunk_row.map(|row| DisplayPoint::new(row, 0)),
4823 newest_cursor_position,
4824 ];
4825
4826 for (hunk, _) in display_hunks {
4827 if let DisplayDiffHunk::Unfolded {
4828 display_row_range,
4829 multi_buffer_range,
4830 status,
4831 is_created_file,
4832 ..
4833 } = &hunk
4834 {
4835 if display_row_range.start < row_range.start
4836 || display_row_range.start >= row_range.end
4837 {
4838 continue;
4839 }
4840 if highlighted_rows
4841 .get(&display_row_range.start)
4842 .and_then(|highlight| highlight.type_id)
4843 .is_some_and(|type_id| {
4844 [
4845 TypeId::of::<ConflictsOuter>(),
4846 TypeId::of::<ConflictsOursMarker>(),
4847 TypeId::of::<ConflictsOurs>(),
4848 TypeId::of::<ConflictsTheirs>(),
4849 TypeId::of::<ConflictsTheirsMarker>(),
4850 ]
4851 .contains(&type_id)
4852 })
4853 {
4854 continue;
4855 }
4856 let row_ix = (display_row_range.start - row_range.start).0 as usize;
4857 if row_infos[row_ix].diff_status.is_none() {
4858 continue;
4859 }
4860 if row_infos[row_ix]
4861 .diff_status
4862 .is_some_and(|status| status.is_added())
4863 && !status.is_added()
4864 {
4865 continue;
4866 }
4867
4868 if active_positions
4869 .iter()
4870 .any(|p| p.map_or(false, |p| display_row_range.contains(&p.row())))
4871 {
4872 let y = display_row_range.start.as_f32() * line_height
4873 + text_hitbox.bounds.top()
4874 - scroll_pixel_position.y;
4875
4876 let mut element = render_diff_hunk_controls(
4877 display_row_range.start.0,
4878 status,
4879 multi_buffer_range.clone(),
4880 *is_created_file,
4881 line_height,
4882 &editor,
4883 window,
4884 cx,
4885 );
4886 let size =
4887 element.layout_as_root(size(px(100.0), line_height).into(), window, cx);
4888
4889 let x = text_hitbox.bounds.right() - right_margin - px(10.) - size.width;
4890
4891 let bounds = Bounds::new(gpui::Point::new(x, y), size);
4892 control_bounds.push((display_row_range.start, bounds));
4893
4894 window.with_absolute_element_offset(gpui::Point::new(x, y), |window| {
4895 element.prepaint(window, cx)
4896 });
4897 controls.push(element);
4898 }
4899 }
4900 }
4901
4902 (controls, control_bounds)
4903 }
4904
4905 fn layout_signature_help(
4906 &self,
4907 hitbox: &Hitbox,
4908 content_origin: gpui::Point<Pixels>,
4909 scroll_pixel_position: gpui::Point<Pixels>,
4910 newest_selection_head: Option<DisplayPoint>,
4911 start_row: DisplayRow,
4912 line_layouts: &[LineWithInvisibles],
4913 line_height: Pixels,
4914 em_width: Pixels,
4915 context_menu_layout: Option<ContextMenuLayout>,
4916 window: &mut Window,
4917 cx: &mut App,
4918 ) {
4919 if !self.editor.focus_handle(cx).is_focused(window) {
4920 return;
4921 }
4922 let Some(newest_selection_head) = newest_selection_head else {
4923 return;
4924 };
4925
4926 let max_size = size(
4927 (120. * em_width) // Default size
4928 .min(hitbox.size.width / 2.) // Shrink to half of the editor width
4929 .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
4930 (16. * line_height) // Default size
4931 .min(hitbox.size.height / 2.) // Shrink to half of the editor height
4932 .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
4933 );
4934
4935 let maybe_element = self.editor.update(cx, |editor, cx| {
4936 if let Some(popover) = editor.signature_help_state.popover_mut() {
4937 let element = popover.render(max_size, cx);
4938 Some(element)
4939 } else {
4940 None
4941 }
4942 });
4943 let Some(mut element) = maybe_element else {
4944 return;
4945 };
4946
4947 let selection_row = newest_selection_head.row();
4948 let Some(cursor_row_layout) = (selection_row >= start_row)
4949 .then(|| line_layouts.get(selection_row.minus(start_row) as usize))
4950 .flatten()
4951 else {
4952 return;
4953 };
4954
4955 let target_x = cursor_row_layout.x_for_index(newest_selection_head.column() as usize)
4956 - scroll_pixel_position.x;
4957 let target_y = selection_row.as_f32() * line_height - scroll_pixel_position.y;
4958 let target_point = content_origin + point(target_x, target_y);
4959
4960 let actual_size = element.layout_as_root(Size::<AvailableSpace>::default(), window, cx);
4961
4962 let (popover_bounds_above, popover_bounds_below) = {
4963 let horizontal_offset = (hitbox.top_right().x
4964 - POPOVER_RIGHT_OFFSET
4965 - (target_point.x + actual_size.width))
4966 .min(Pixels::ZERO);
4967 let initial_x = target_point.x + horizontal_offset;
4968 (
4969 Bounds::new(
4970 point(initial_x, target_point.y - actual_size.height),
4971 actual_size,
4972 ),
4973 Bounds::new(
4974 point(initial_x, target_point.y + line_height + HOVER_POPOVER_GAP),
4975 actual_size,
4976 ),
4977 )
4978 };
4979
4980 let intersects_menu = |bounds: Bounds<Pixels>| -> bool {
4981 context_menu_layout
4982 .as_ref()
4983 .map_or(false, |menu| bounds.intersects(&menu.bounds))
4984 };
4985
4986 let final_origin = if popover_bounds_above.is_contained_within(hitbox)
4987 && !intersects_menu(popover_bounds_above)
4988 {
4989 // try placing above cursor
4990 popover_bounds_above.origin
4991 } else if popover_bounds_below.is_contained_within(hitbox)
4992 && !intersects_menu(popover_bounds_below)
4993 {
4994 // try placing below cursor
4995 popover_bounds_below.origin
4996 } else {
4997 // try surrounding context menu if exists
4998 let origin_surrounding_menu = context_menu_layout.as_ref().and_then(|menu| {
4999 let y_for_horizontal_positioning = if menu.y_flipped {
5000 menu.bounds.bottom() - actual_size.height
5001 } else {
5002 menu.bounds.top()
5003 };
5004 let possible_origins = vec![
5005 // left of context menu
5006 point(
5007 menu.bounds.left() - actual_size.width - HOVER_POPOVER_GAP,
5008 y_for_horizontal_positioning,
5009 ),
5010 // right of context menu
5011 point(
5012 menu.bounds.right() + HOVER_POPOVER_GAP,
5013 y_for_horizontal_positioning,
5014 ),
5015 // top of context menu
5016 point(
5017 menu.bounds.left(),
5018 menu.bounds.top() - actual_size.height - HOVER_POPOVER_GAP,
5019 ),
5020 // bottom of context menu
5021 point(menu.bounds.left(), menu.bounds.bottom() + HOVER_POPOVER_GAP),
5022 ];
5023 possible_origins
5024 .into_iter()
5025 .find(|&origin| Bounds::new(origin, actual_size).is_contained_within(hitbox))
5026 });
5027 origin_surrounding_menu.unwrap_or_else(|| {
5028 // fallback to existing above/below cursor logic
5029 // this might overlap menu or overflow in rare case
5030 if popover_bounds_above.is_contained_within(hitbox) {
5031 popover_bounds_above.origin
5032 } else {
5033 popover_bounds_below.origin
5034 }
5035 })
5036 };
5037
5038 window.defer_draw(element, final_origin, 2);
5039 }
5040
5041 fn paint_background(&self, layout: &EditorLayout, window: &mut Window, cx: &mut App) {
5042 window.paint_layer(layout.hitbox.bounds, |window| {
5043 let scroll_top = layout.position_map.snapshot.scroll_position().y;
5044 let gutter_bg = cx.theme().colors().editor_gutter_background;
5045 window.paint_quad(fill(layout.gutter_hitbox.bounds, gutter_bg));
5046 window.paint_quad(fill(
5047 layout.position_map.text_hitbox.bounds,
5048 self.style.background,
5049 ));
5050
5051 if matches!(
5052 layout.mode,
5053 EditorMode::Full { .. } | EditorMode::Minimap { .. }
5054 ) {
5055 let show_active_line_background = match layout.mode {
5056 EditorMode::Full {
5057 show_active_line_background,
5058 ..
5059 } => show_active_line_background,
5060 EditorMode::Minimap { .. } => true,
5061 _ => false,
5062 };
5063 let mut active_rows = layout.active_rows.iter().peekable();
5064 while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
5065 let mut end_row = start_row.0;
5066 while active_rows
5067 .peek()
5068 .map_or(false, |(active_row, has_selection)| {
5069 active_row.0 == end_row + 1
5070 && has_selection.selection == contains_non_empty_selection.selection
5071 })
5072 {
5073 active_rows.next().unwrap();
5074 end_row += 1;
5075 }
5076
5077 if show_active_line_background && !contains_non_empty_selection.selection {
5078 let highlight_h_range =
5079 match layout.position_map.snapshot.current_line_highlight {
5080 CurrentLineHighlight::Gutter => Some(Range {
5081 start: layout.hitbox.left(),
5082 end: layout.gutter_hitbox.right(),
5083 }),
5084 CurrentLineHighlight::Line => Some(Range {
5085 start: layout.position_map.text_hitbox.bounds.left(),
5086 end: layout.position_map.text_hitbox.bounds.right(),
5087 }),
5088 CurrentLineHighlight::All => Some(Range {
5089 start: layout.hitbox.left(),
5090 end: layout.hitbox.right(),
5091 }),
5092 CurrentLineHighlight::None => None,
5093 };
5094 if let Some(range) = highlight_h_range {
5095 let active_line_bg = cx.theme().colors().editor_active_line_background;
5096 let bounds = Bounds {
5097 origin: point(
5098 range.start,
5099 layout.hitbox.origin.y
5100 + (start_row.as_f32() - scroll_top)
5101 * layout.position_map.line_height,
5102 ),
5103 size: size(
5104 range.end - range.start,
5105 layout.position_map.line_height
5106 * (end_row - start_row.0 + 1) as f32,
5107 ),
5108 };
5109 window.paint_quad(fill(bounds, active_line_bg));
5110 }
5111 }
5112 }
5113
5114 let mut paint_highlight = |highlight_row_start: DisplayRow,
5115 highlight_row_end: DisplayRow,
5116 highlight: crate::LineHighlight,
5117 edges| {
5118 let mut origin_x = layout.hitbox.left();
5119 let mut width = layout.hitbox.size.width;
5120 if !highlight.include_gutter {
5121 origin_x += layout.gutter_hitbox.size.width;
5122 width -= layout.gutter_hitbox.size.width;
5123 }
5124
5125 let origin = point(
5126 origin_x,
5127 layout.hitbox.origin.y
5128 + (highlight_row_start.as_f32() - scroll_top)
5129 * layout.position_map.line_height,
5130 );
5131 let size = size(
5132 width,
5133 layout.position_map.line_height
5134 * highlight_row_end.next_row().minus(highlight_row_start) as f32,
5135 );
5136 let mut quad = fill(Bounds { origin, size }, highlight.background);
5137 if let Some(border_color) = highlight.border {
5138 quad.border_color = border_color;
5139 quad.border_widths = edges
5140 }
5141 window.paint_quad(quad);
5142 };
5143
5144 let mut current_paint: Option<(LineHighlight, Range<DisplayRow>, Edges<Pixels>)> =
5145 None;
5146 for (&new_row, &new_background) in &layout.highlighted_rows {
5147 match &mut current_paint {
5148 &mut Some((current_background, ref mut current_range, mut edges)) => {
5149 let new_range_started = current_background != new_background
5150 || current_range.end.next_row() != new_row;
5151 if new_range_started {
5152 if current_range.end.next_row() == new_row {
5153 edges.bottom = px(0.);
5154 };
5155 paint_highlight(
5156 current_range.start,
5157 current_range.end,
5158 current_background,
5159 edges,
5160 );
5161 let edges = Edges {
5162 top: if current_range.end.next_row() != new_row {
5163 px(1.)
5164 } else {
5165 px(0.)
5166 },
5167 bottom: px(1.),
5168 ..Default::default()
5169 };
5170 current_paint = Some((new_background, new_row..new_row, edges));
5171 continue;
5172 } else {
5173 current_range.end = current_range.end.next_row();
5174 }
5175 }
5176 None => {
5177 let edges = Edges {
5178 top: px(1.),
5179 bottom: px(1.),
5180 ..Default::default()
5181 };
5182 current_paint = Some((new_background, new_row..new_row, edges))
5183 }
5184 };
5185 }
5186 if let Some((color, range, edges)) = current_paint {
5187 paint_highlight(range.start, range.end, color, edges);
5188 }
5189
5190 let scroll_left =
5191 layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
5192
5193 for (wrap_position, active) in layout.wrap_guides.iter() {
5194 let x = (layout.position_map.text_hitbox.origin.x
5195 + *wrap_position
5196 + layout.position_map.em_width / 2.)
5197 - scroll_left;
5198
5199 let show_scrollbars = layout
5200 .scrollbars_layout
5201 .as_ref()
5202 .map_or(false, |layout| layout.visible);
5203
5204 if x < layout.position_map.text_hitbox.origin.x
5205 || (show_scrollbars && x > self.scrollbar_left(&layout.hitbox.bounds))
5206 {
5207 continue;
5208 }
5209
5210 let color = if *active {
5211 cx.theme().colors().editor_active_wrap_guide
5212 } else {
5213 cx.theme().colors().editor_wrap_guide
5214 };
5215 window.paint_quad(fill(
5216 Bounds {
5217 origin: point(x, layout.position_map.text_hitbox.origin.y),
5218 size: size(px(1.), layout.position_map.text_hitbox.size.height),
5219 },
5220 color,
5221 ));
5222 }
5223 }
5224 })
5225 }
5226
5227 fn paint_indent_guides(
5228 &mut self,
5229 layout: &mut EditorLayout,
5230 window: &mut Window,
5231 cx: &mut App,
5232 ) {
5233 let Some(indent_guides) = &layout.indent_guides else {
5234 return;
5235 };
5236
5237 let faded_color = |color: Hsla, alpha: f32| {
5238 let mut faded = color;
5239 faded.a = alpha;
5240 faded
5241 };
5242
5243 for indent_guide in indent_guides {
5244 let indent_accent_colors = cx.theme().accents().color_for_index(indent_guide.depth);
5245 let settings = indent_guide.settings;
5246
5247 // TODO fixed for now, expose them through themes later
5248 const INDENT_AWARE_ALPHA: f32 = 0.2;
5249 const INDENT_AWARE_ACTIVE_ALPHA: f32 = 0.4;
5250 const INDENT_AWARE_BACKGROUND_ALPHA: f32 = 0.1;
5251 const INDENT_AWARE_BACKGROUND_ACTIVE_ALPHA: f32 = 0.2;
5252
5253 let line_color = match (settings.coloring, indent_guide.active) {
5254 (IndentGuideColoring::Disabled, _) => None,
5255 (IndentGuideColoring::Fixed, false) => {
5256 Some(cx.theme().colors().editor_indent_guide)
5257 }
5258 (IndentGuideColoring::Fixed, true) => {
5259 Some(cx.theme().colors().editor_indent_guide_active)
5260 }
5261 (IndentGuideColoring::IndentAware, false) => {
5262 Some(faded_color(indent_accent_colors, INDENT_AWARE_ALPHA))
5263 }
5264 (IndentGuideColoring::IndentAware, true) => {
5265 Some(faded_color(indent_accent_colors, INDENT_AWARE_ACTIVE_ALPHA))
5266 }
5267 };
5268
5269 let background_color = match (settings.background_coloring, indent_guide.active) {
5270 (IndentGuideBackgroundColoring::Disabled, _) => None,
5271 (IndentGuideBackgroundColoring::IndentAware, false) => Some(faded_color(
5272 indent_accent_colors,
5273 INDENT_AWARE_BACKGROUND_ALPHA,
5274 )),
5275 (IndentGuideBackgroundColoring::IndentAware, true) => Some(faded_color(
5276 indent_accent_colors,
5277 INDENT_AWARE_BACKGROUND_ACTIVE_ALPHA,
5278 )),
5279 };
5280
5281 let requested_line_width = if indent_guide.active {
5282 settings.active_line_width
5283 } else {
5284 settings.line_width
5285 }
5286 .clamp(1, 10);
5287 let mut line_indicator_width = 0.;
5288 if let Some(color) = line_color {
5289 window.paint_quad(fill(
5290 Bounds {
5291 origin: indent_guide.origin,
5292 size: size(px(requested_line_width as f32), indent_guide.length),
5293 },
5294 color,
5295 ));
5296 line_indicator_width = requested_line_width as f32;
5297 }
5298
5299 if let Some(color) = background_color {
5300 let width = indent_guide.single_indent_width - px(line_indicator_width);
5301 window.paint_quad(fill(
5302 Bounds {
5303 origin: point(
5304 indent_guide.origin.x + px(line_indicator_width),
5305 indent_guide.origin.y,
5306 ),
5307 size: size(width, indent_guide.length),
5308 },
5309 color,
5310 ));
5311 }
5312 }
5313 }
5314
5315 fn paint_line_numbers(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
5316 let is_singleton = self.editor.read(cx).is_singleton(cx);
5317
5318 let line_height = layout.position_map.line_height;
5319 window.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox);
5320
5321 for LineNumberLayout {
5322 shaped_line,
5323 hitbox,
5324 } in layout.line_numbers.values()
5325 {
5326 let Some(hitbox) = hitbox else {
5327 continue;
5328 };
5329
5330 let Some(()) = (if !is_singleton && hitbox.is_hovered(window) {
5331 let color = cx.theme().colors().editor_hover_line_number;
5332
5333 let line = self.shape_line_number(shaped_line.text.clone(), color, window);
5334 line.paint(hitbox.origin, line_height, window, cx).log_err()
5335 } else {
5336 shaped_line
5337 .paint(hitbox.origin, line_height, window, cx)
5338 .log_err()
5339 }) else {
5340 continue;
5341 };
5342
5343 // In singleton buffers, we select corresponding lines on the line number click, so use | -like cursor.
5344 // In multi buffers, we open file at the line number clicked, so use a pointing hand cursor.
5345 if is_singleton {
5346 window.set_cursor_style(CursorStyle::IBeam, &hitbox);
5347 } else {
5348 window.set_cursor_style(CursorStyle::PointingHand, &hitbox);
5349 }
5350 }
5351 }
5352
5353 fn paint_gutter_diff_hunks(layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
5354 if layout.display_hunks.is_empty() {
5355 return;
5356 }
5357
5358 let line_height = layout.position_map.line_height;
5359 window.paint_layer(layout.gutter_hitbox.bounds, |window| {
5360 for (hunk, hitbox) in &layout.display_hunks {
5361 let hunk_to_paint = match hunk {
5362 DisplayDiffHunk::Folded { .. } => {
5363 let hunk_bounds = Self::diff_hunk_bounds(
5364 &layout.position_map.snapshot,
5365 line_height,
5366 layout.gutter_hitbox.bounds,
5367 &hunk,
5368 );
5369 Some((
5370 hunk_bounds,
5371 cx.theme().colors().version_control_modified,
5372 Corners::all(px(0.)),
5373 DiffHunkStatus::modified_none(),
5374 ))
5375 }
5376 DisplayDiffHunk::Unfolded {
5377 status,
5378 display_row_range,
5379 ..
5380 } => hitbox.as_ref().map(|hunk_hitbox| match status.kind {
5381 DiffHunkStatusKind::Added => (
5382 hunk_hitbox.bounds,
5383 cx.theme().colors().version_control_added,
5384 Corners::all(px(0.)),
5385 *status,
5386 ),
5387 DiffHunkStatusKind::Modified => (
5388 hunk_hitbox.bounds,
5389 cx.theme().colors().version_control_modified,
5390 Corners::all(px(0.)),
5391 *status,
5392 ),
5393 DiffHunkStatusKind::Deleted if !display_row_range.is_empty() => (
5394 hunk_hitbox.bounds,
5395 cx.theme().colors().version_control_deleted,
5396 Corners::all(px(0.)),
5397 *status,
5398 ),
5399 DiffHunkStatusKind::Deleted => (
5400 Bounds::new(
5401 point(
5402 hunk_hitbox.origin.x - hunk_hitbox.size.width,
5403 hunk_hitbox.origin.y,
5404 ),
5405 size(hunk_hitbox.size.width * 2., hunk_hitbox.size.height),
5406 ),
5407 cx.theme().colors().version_control_deleted,
5408 Corners::all(1. * line_height),
5409 *status,
5410 ),
5411 }),
5412 };
5413
5414 if let Some((hunk_bounds, background_color, corner_radii, status)) = hunk_to_paint {
5415 // Flatten the background color with the editor color to prevent
5416 // elements below transparent hunks from showing through
5417 let flattened_background_color = cx
5418 .theme()
5419 .colors()
5420 .editor_background
5421 .blend(background_color);
5422
5423 if !Self::diff_hunk_hollow(status, cx) {
5424 window.paint_quad(quad(
5425 hunk_bounds,
5426 corner_radii,
5427 flattened_background_color,
5428 Edges::default(),
5429 transparent_black(),
5430 BorderStyle::default(),
5431 ));
5432 } else {
5433 let flattened_unstaged_background_color = cx
5434 .theme()
5435 .colors()
5436 .editor_background
5437 .blend(background_color.opacity(0.3));
5438
5439 window.paint_quad(quad(
5440 hunk_bounds,
5441 corner_radii,
5442 flattened_unstaged_background_color,
5443 Edges::all(Pixels(1.0)),
5444 flattened_background_color,
5445 BorderStyle::Solid,
5446 ));
5447 }
5448 }
5449 }
5450 });
5451 }
5452
5453 fn gutter_strip_width(line_height: Pixels) -> Pixels {
5454 (0.275 * line_height).floor()
5455 }
5456
5457 fn diff_hunk_bounds(
5458 snapshot: &EditorSnapshot,
5459 line_height: Pixels,
5460 gutter_bounds: Bounds<Pixels>,
5461 hunk: &DisplayDiffHunk,
5462 ) -> Bounds<Pixels> {
5463 let scroll_position = snapshot.scroll_position();
5464 let scroll_top = scroll_position.y * line_height;
5465 let gutter_strip_width = Self::gutter_strip_width(line_height);
5466
5467 match hunk {
5468 DisplayDiffHunk::Folded { display_row, .. } => {
5469 let start_y = display_row.as_f32() * line_height - scroll_top;
5470 let end_y = start_y + line_height;
5471 let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
5472 let highlight_size = size(gutter_strip_width, end_y - start_y);
5473 Bounds::new(highlight_origin, highlight_size)
5474 }
5475 DisplayDiffHunk::Unfolded {
5476 display_row_range,
5477 status,
5478 ..
5479 } => {
5480 if status.is_deleted() && display_row_range.is_empty() {
5481 let row = display_row_range.start;
5482
5483 let offset = line_height / 2.;
5484 let start_y = row.as_f32() * line_height - offset - scroll_top;
5485 let end_y = start_y + line_height;
5486
5487 let width = (0.35 * line_height).floor();
5488 let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
5489 let highlight_size = size(width, end_y - start_y);
5490 Bounds::new(highlight_origin, highlight_size)
5491 } else {
5492 let start_row = display_row_range.start;
5493 let end_row = display_row_range.end;
5494 // If we're in a multibuffer, row range span might include an
5495 // excerpt header, so if we were to draw the marker straight away,
5496 // the hunk might include the rows of that header.
5497 // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap.
5498 // Instead, we simply check whether the range we're dealing with includes
5499 // any excerpt headers and if so, we stop painting the diff hunk on the first row of that header.
5500 let end_row_in_current_excerpt = snapshot
5501 .blocks_in_range(start_row..end_row)
5502 .find_map(|(start_row, block)| {
5503 if matches!(block, Block::ExcerptBoundary { .. }) {
5504 Some(start_row)
5505 } else {
5506 None
5507 }
5508 })
5509 .unwrap_or(end_row);
5510
5511 let start_y = start_row.as_f32() * line_height - scroll_top;
5512 let end_y = end_row_in_current_excerpt.as_f32() * line_height - scroll_top;
5513
5514 let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
5515 let highlight_size = size(gutter_strip_width, end_y - start_y);
5516 Bounds::new(highlight_origin, highlight_size)
5517 }
5518 }
5519 }
5520 }
5521
5522 fn paint_gutter_indicators(
5523 &self,
5524 layout: &mut EditorLayout,
5525 window: &mut Window,
5526 cx: &mut App,
5527 ) {
5528 window.paint_layer(layout.gutter_hitbox.bounds, |window| {
5529 window.with_element_namespace("crease_toggles", |window| {
5530 for crease_toggle in layout.crease_toggles.iter_mut().flatten() {
5531 crease_toggle.paint(window, cx);
5532 }
5533 });
5534
5535 window.with_element_namespace("expand_toggles", |window| {
5536 for (expand_toggle, _) in layout.expand_toggles.iter_mut().flatten() {
5537 expand_toggle.paint(window, cx);
5538 }
5539 });
5540
5541 for breakpoint in layout.breakpoints.iter_mut() {
5542 breakpoint.paint(window, cx);
5543 }
5544
5545 for test_indicator in layout.test_indicators.iter_mut() {
5546 test_indicator.paint(window, cx);
5547 }
5548 });
5549 }
5550
5551 fn paint_gutter_highlights(
5552 &self,
5553 layout: &mut EditorLayout,
5554 window: &mut Window,
5555 cx: &mut App,
5556 ) {
5557 for (_, hunk_hitbox) in &layout.display_hunks {
5558 if let Some(hunk_hitbox) = hunk_hitbox {
5559 if !self
5560 .editor
5561 .read(cx)
5562 .buffer()
5563 .read(cx)
5564 .all_diff_hunks_expanded()
5565 {
5566 window.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox);
5567 }
5568 }
5569 }
5570
5571 let show_git_gutter = layout
5572 .position_map
5573 .snapshot
5574 .show_git_diff_gutter
5575 .unwrap_or_else(|| {
5576 matches!(
5577 ProjectSettings::get_global(cx).git.git_gutter,
5578 Some(GitGutterSetting::TrackedFiles)
5579 )
5580 });
5581 if show_git_gutter {
5582 Self::paint_gutter_diff_hunks(layout, window, cx)
5583 }
5584
5585 let highlight_width = 0.275 * layout.position_map.line_height;
5586 let highlight_corner_radii = Corners::all(0.05 * layout.position_map.line_height);
5587 window.paint_layer(layout.gutter_hitbox.bounds, |window| {
5588 for (range, color) in &layout.highlighted_gutter_ranges {
5589 let start_row = if range.start.row() < layout.visible_display_row_range.start {
5590 layout.visible_display_row_range.start - DisplayRow(1)
5591 } else {
5592 range.start.row()
5593 };
5594 let end_row = if range.end.row() > layout.visible_display_row_range.end {
5595 layout.visible_display_row_range.end + DisplayRow(1)
5596 } else {
5597 range.end.row()
5598 };
5599
5600 let start_y = layout.gutter_hitbox.top()
5601 + start_row.0 as f32 * layout.position_map.line_height
5602 - layout.position_map.scroll_pixel_position.y;
5603 let end_y = layout.gutter_hitbox.top()
5604 + (end_row.0 + 1) as f32 * layout.position_map.line_height
5605 - layout.position_map.scroll_pixel_position.y;
5606 let bounds = Bounds::from_corners(
5607 point(layout.gutter_hitbox.left(), start_y),
5608 point(layout.gutter_hitbox.left() + highlight_width, end_y),
5609 );
5610 window.paint_quad(fill(bounds, *color).corner_radii(highlight_corner_radii));
5611 }
5612 });
5613 }
5614
5615 fn paint_blamed_display_rows(
5616 &self,
5617 layout: &mut EditorLayout,
5618 window: &mut Window,
5619 cx: &mut App,
5620 ) {
5621 let Some(blamed_display_rows) = layout.blamed_display_rows.take() else {
5622 return;
5623 };
5624
5625 window.paint_layer(layout.gutter_hitbox.bounds, |window| {
5626 for mut blame_element in blamed_display_rows.into_iter() {
5627 blame_element.paint(window, cx);
5628 }
5629 })
5630 }
5631
5632 fn paint_text(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
5633 window.with_content_mask(
5634 Some(ContentMask {
5635 bounds: layout.position_map.text_hitbox.bounds,
5636 }),
5637 |window| {
5638 let editor = self.editor.read(cx);
5639 if editor.mouse_cursor_hidden {
5640 window.set_window_cursor_style(CursorStyle::None);
5641 } else if matches!(
5642 editor.selection_drag_state,
5643 SelectionDragState::Dragging { .. }
5644 ) {
5645 window
5646 .set_cursor_style(CursorStyle::DragCopy, &layout.position_map.text_hitbox);
5647 } else if editor
5648 .hovered_link_state
5649 .as_ref()
5650 .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
5651 {
5652 window.set_cursor_style(
5653 CursorStyle::PointingHand,
5654 &layout.position_map.text_hitbox,
5655 );
5656 } else {
5657 window.set_cursor_style(CursorStyle::IBeam, &layout.position_map.text_hitbox);
5658 };
5659
5660 self.paint_lines_background(layout, window, cx);
5661 let invisible_display_ranges = self.paint_highlights(layout, window);
5662 self.paint_lines(&invisible_display_ranges, layout, window, cx);
5663 self.paint_redactions(layout, window);
5664 self.paint_cursors(layout, window, cx);
5665 self.paint_inline_diagnostics(layout, window, cx);
5666 self.paint_inline_blame(layout, window, cx);
5667 self.paint_inline_code_actions(layout, window, cx);
5668 self.paint_diff_hunk_controls(layout, window, cx);
5669 window.with_element_namespace("crease_trailers", |window| {
5670 for trailer in layout.crease_trailers.iter_mut().flatten() {
5671 trailer.element.paint(window, cx);
5672 }
5673 });
5674 },
5675 )
5676 }
5677
5678 fn paint_highlights(
5679 &mut self,
5680 layout: &mut EditorLayout,
5681 window: &mut Window,
5682 ) -> SmallVec<[Range<DisplayPoint>; 32]> {
5683 window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
5684 let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
5685 let line_end_overshoot = 0.15 * layout.position_map.line_height;
5686 for (range, color) in &layout.highlighted_ranges {
5687 self.paint_highlighted_range(
5688 range.clone(),
5689 *color,
5690 Pixels::ZERO,
5691 line_end_overshoot,
5692 layout,
5693 window,
5694 );
5695 }
5696
5697 let corner_radius = 0.15 * layout.position_map.line_height;
5698
5699 for (player_color, selections) in &layout.selections {
5700 for selection in selections.iter() {
5701 self.paint_highlighted_range(
5702 selection.range.clone(),
5703 player_color.selection,
5704 corner_radius,
5705 corner_radius * 2.,
5706 layout,
5707 window,
5708 );
5709
5710 if selection.is_local && !selection.range.is_empty() {
5711 invisible_display_ranges.push(selection.range.clone());
5712 }
5713 }
5714 }
5715 invisible_display_ranges
5716 })
5717 }
5718
5719 fn paint_lines(
5720 &mut self,
5721 invisible_display_ranges: &[Range<DisplayPoint>],
5722 layout: &mut EditorLayout,
5723 window: &mut Window,
5724 cx: &mut App,
5725 ) {
5726 let whitespace_setting = self
5727 .editor
5728 .read(cx)
5729 .buffer
5730 .read(cx)
5731 .language_settings(cx)
5732 .show_whitespaces;
5733
5734 for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
5735 let row = DisplayRow(layout.visible_display_row_range.start.0 + ix as u32);
5736 line_with_invisibles.draw(
5737 layout,
5738 row,
5739 layout.content_origin,
5740 whitespace_setting,
5741 invisible_display_ranges,
5742 window,
5743 cx,
5744 )
5745 }
5746
5747 for line_element in &mut layout.line_elements {
5748 line_element.paint(window, cx);
5749 }
5750 }
5751
5752 fn paint_lines_background(
5753 &mut self,
5754 layout: &mut EditorLayout,
5755 window: &mut Window,
5756 cx: &mut App,
5757 ) {
5758 for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
5759 let row = DisplayRow(layout.visible_display_row_range.start.0 + ix as u32);
5760 line_with_invisibles.draw_background(layout, row, layout.content_origin, window, cx);
5761 }
5762 }
5763
5764 fn paint_redactions(&mut self, layout: &EditorLayout, window: &mut Window) {
5765 if layout.redacted_ranges.is_empty() {
5766 return;
5767 }
5768
5769 let line_end_overshoot = layout.line_end_overshoot();
5770
5771 // A softer than perfect black
5772 let redaction_color = gpui::rgb(0x0e1111);
5773
5774 window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
5775 for range in layout.redacted_ranges.iter() {
5776 self.paint_highlighted_range(
5777 range.clone(),
5778 redaction_color.into(),
5779 Pixels::ZERO,
5780 line_end_overshoot,
5781 layout,
5782 window,
5783 );
5784 }
5785 });
5786 }
5787
5788 fn paint_cursors(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
5789 for cursor in &mut layout.visible_cursors {
5790 cursor.paint(layout.content_origin, window, cx);
5791 }
5792 }
5793
5794 fn paint_scrollbars(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
5795 let Some(scrollbars_layout) = layout.scrollbars_layout.take() else {
5796 return;
5797 };
5798 let any_scrollbar_dragged = self.editor.read(cx).scroll_manager.any_scrollbar_dragged();
5799
5800 for (scrollbar_layout, axis) in scrollbars_layout.iter_scrollbars() {
5801 let hitbox = &scrollbar_layout.hitbox;
5802 if scrollbars_layout.visible {
5803 let scrollbar_edges = match axis {
5804 ScrollbarAxis::Horizontal => Edges {
5805 top: Pixels::ZERO,
5806 right: Pixels::ZERO,
5807 bottom: Pixels::ZERO,
5808 left: Pixels::ZERO,
5809 },
5810 ScrollbarAxis::Vertical => Edges {
5811 top: Pixels::ZERO,
5812 right: Pixels::ZERO,
5813 bottom: Pixels::ZERO,
5814 left: ScrollbarLayout::BORDER_WIDTH,
5815 },
5816 };
5817
5818 window.paint_layer(hitbox.bounds, |window| {
5819 window.paint_quad(quad(
5820 hitbox.bounds,
5821 Corners::default(),
5822 cx.theme().colors().scrollbar_track_background,
5823 scrollbar_edges,
5824 cx.theme().colors().scrollbar_track_border,
5825 BorderStyle::Solid,
5826 ));
5827
5828 if axis == ScrollbarAxis::Vertical {
5829 let fast_markers =
5830 self.collect_fast_scrollbar_markers(layout, &scrollbar_layout, cx);
5831 // Refresh slow scrollbar markers in the background. Below, we
5832 // paint whatever markers have already been computed.
5833 self.refresh_slow_scrollbar_markers(layout, &scrollbar_layout, window, cx);
5834
5835 let markers = self.editor.read(cx).scrollbar_marker_state.markers.clone();
5836 for marker in markers.iter().chain(&fast_markers) {
5837 let mut marker = marker.clone();
5838 marker.bounds.origin += hitbox.origin;
5839 window.paint_quad(marker);
5840 }
5841 }
5842
5843 if let Some(thumb_bounds) = scrollbar_layout.thumb_bounds {
5844 let scrollbar_thumb_color = match scrollbar_layout.thumb_state {
5845 ScrollbarThumbState::Dragging => {
5846 cx.theme().colors().scrollbar_thumb_active_background
5847 }
5848 ScrollbarThumbState::Hovered => {
5849 cx.theme().colors().scrollbar_thumb_hover_background
5850 }
5851 ScrollbarThumbState::Idle => {
5852 cx.theme().colors().scrollbar_thumb_background
5853 }
5854 };
5855 window.paint_quad(quad(
5856 thumb_bounds,
5857 Corners::default(),
5858 scrollbar_thumb_color,
5859 scrollbar_edges,
5860 cx.theme().colors().scrollbar_thumb_border,
5861 BorderStyle::Solid,
5862 ));
5863
5864 if any_scrollbar_dragged {
5865 window.set_window_cursor_style(CursorStyle::Arrow);
5866 } else {
5867 window.set_cursor_style(CursorStyle::Arrow, &hitbox);
5868 }
5869 }
5870 })
5871 }
5872 }
5873
5874 window.on_mouse_event({
5875 let editor = self.editor.clone();
5876 let scrollbars_layout = scrollbars_layout.clone();
5877
5878 let mut mouse_position = window.mouse_position();
5879 move |event: &MouseMoveEvent, phase, window, cx| {
5880 if phase == DispatchPhase::Capture {
5881 return;
5882 }
5883
5884 editor.update(cx, |editor, cx| {
5885 if let Some((scrollbar_layout, axis)) = event
5886 .pressed_button
5887 .filter(|button| *button == MouseButton::Left)
5888 .and(editor.scroll_manager.dragging_scrollbar_axis())
5889 .and_then(|axis| {
5890 scrollbars_layout
5891 .iter_scrollbars()
5892 .find(|(_, a)| *a == axis)
5893 })
5894 {
5895 let ScrollbarLayout {
5896 hitbox,
5897 text_unit_size,
5898 ..
5899 } = scrollbar_layout;
5900
5901 let old_position = mouse_position.along(axis);
5902 let new_position = event.position.along(axis);
5903 if (hitbox.origin.along(axis)..hitbox.bottom_right().along(axis))
5904 .contains(&old_position)
5905 {
5906 let position = editor.scroll_position(cx).apply_along(axis, |p| {
5907 (p + (new_position - old_position) / *text_unit_size).max(0.)
5908 });
5909 editor.set_scroll_position(position, window, cx);
5910 }
5911
5912 editor.scroll_manager.show_scrollbars(window, cx);
5913 cx.stop_propagation();
5914 } else if let Some((layout, axis)) = scrollbars_layout
5915 .get_hovered_axis(window)
5916 .filter(|_| !event.dragging())
5917 {
5918 if layout.thumb_hovered(&event.position) {
5919 editor
5920 .scroll_manager
5921 .set_hovered_scroll_thumb_axis(axis, cx);
5922 } else {
5923 editor.scroll_manager.reset_scrollbar_state(cx);
5924 }
5925
5926 editor.scroll_manager.show_scrollbars(window, cx);
5927 } else {
5928 editor.scroll_manager.reset_scrollbar_state(cx);
5929 }
5930
5931 mouse_position = event.position;
5932 })
5933 }
5934 });
5935
5936 if any_scrollbar_dragged {
5937 window.on_mouse_event({
5938 let editor = self.editor.clone();
5939 move |_: &MouseUpEvent, phase, window, cx| {
5940 if phase == DispatchPhase::Capture {
5941 return;
5942 }
5943
5944 editor.update(cx, |editor, cx| {
5945 if let Some((_, axis)) = scrollbars_layout.get_hovered_axis(window) {
5946 editor
5947 .scroll_manager
5948 .set_hovered_scroll_thumb_axis(axis, cx);
5949 } else {
5950 editor.scroll_manager.reset_scrollbar_state(cx);
5951 }
5952 cx.stop_propagation();
5953 });
5954 }
5955 });
5956 } else {
5957 window.on_mouse_event({
5958 let editor = self.editor.clone();
5959
5960 move |event: &MouseDownEvent, phase, window, cx| {
5961 if phase == DispatchPhase::Capture {
5962 return;
5963 }
5964 let Some((scrollbar_layout, axis)) = scrollbars_layout.get_hovered_axis(window)
5965 else {
5966 return;
5967 };
5968
5969 let ScrollbarLayout {
5970 hitbox,
5971 visible_range,
5972 text_unit_size,
5973 thumb_bounds,
5974 ..
5975 } = scrollbar_layout;
5976
5977 let Some(thumb_bounds) = thumb_bounds else {
5978 return;
5979 };
5980
5981 editor.update(cx, |editor, cx| {
5982 editor
5983 .scroll_manager
5984 .set_dragged_scroll_thumb_axis(axis, cx);
5985
5986 let event_position = event.position.along(axis);
5987
5988 if event_position < thumb_bounds.origin.along(axis)
5989 || thumb_bounds.bottom_right().along(axis) < event_position
5990 {
5991 let center_position = ((event_position - hitbox.origin.along(axis))
5992 / *text_unit_size)
5993 .round() as u32;
5994 let start_position = center_position.saturating_sub(
5995 (visible_range.end - visible_range.start) as u32 / 2,
5996 );
5997
5998 let position = editor
5999 .scroll_position(cx)
6000 .apply_along(axis, |_| start_position as f32);
6001
6002 editor.set_scroll_position(position, window, cx);
6003 } else {
6004 editor.scroll_manager.show_scrollbars(window, cx);
6005 }
6006
6007 cx.stop_propagation();
6008 });
6009 }
6010 });
6011 }
6012 }
6013
6014 fn collect_fast_scrollbar_markers(
6015 &self,
6016 layout: &EditorLayout,
6017 scrollbar_layout: &ScrollbarLayout,
6018 cx: &mut App,
6019 ) -> Vec<PaintQuad> {
6020 const LIMIT: usize = 100;
6021 if !EditorSettings::get_global(cx).scrollbar.cursors || layout.cursors.len() > LIMIT {
6022 return vec![];
6023 }
6024 let cursor_ranges = layout
6025 .cursors
6026 .iter()
6027 .map(|(point, color)| ColoredRange {
6028 start: point.row(),
6029 end: point.row(),
6030 color: *color,
6031 })
6032 .collect_vec();
6033 scrollbar_layout.marker_quads_for_ranges(cursor_ranges, None)
6034 }
6035
6036 fn refresh_slow_scrollbar_markers(
6037 &self,
6038 layout: &EditorLayout,
6039 scrollbar_layout: &ScrollbarLayout,
6040 window: &mut Window,
6041 cx: &mut App,
6042 ) {
6043 self.editor.update(cx, |editor, cx| {
6044 if !editor.is_singleton(cx)
6045 || !editor
6046 .scrollbar_marker_state
6047 .should_refresh(scrollbar_layout.hitbox.size)
6048 {
6049 return;
6050 }
6051
6052 let scrollbar_layout = scrollbar_layout.clone();
6053 let background_highlights = editor.background_highlights.clone();
6054 let snapshot = layout.position_map.snapshot.clone();
6055 let theme = cx.theme().clone();
6056 let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
6057
6058 editor.scrollbar_marker_state.dirty = false;
6059 editor.scrollbar_marker_state.pending_refresh =
6060 Some(cx.spawn_in(window, async move |editor, cx| {
6061 let scrollbar_size = scrollbar_layout.hitbox.size;
6062 let scrollbar_markers = cx
6063 .background_spawn(async move {
6064 let max_point = snapshot.display_snapshot.buffer_snapshot.max_point();
6065 let mut marker_quads = Vec::new();
6066 if scrollbar_settings.git_diff {
6067 let marker_row_ranges =
6068 snapshot.buffer_snapshot.diff_hunks().map(|hunk| {
6069 let start_display_row =
6070 MultiBufferPoint::new(hunk.row_range.start.0, 0)
6071 .to_display_point(&snapshot.display_snapshot)
6072 .row();
6073 let mut end_display_row =
6074 MultiBufferPoint::new(hunk.row_range.end.0, 0)
6075 .to_display_point(&snapshot.display_snapshot)
6076 .row();
6077 if end_display_row != start_display_row {
6078 end_display_row.0 -= 1;
6079 }
6080 let color = match &hunk.status().kind {
6081 DiffHunkStatusKind::Added => {
6082 theme.colors().version_control_added
6083 }
6084 DiffHunkStatusKind::Modified => {
6085 theme.colors().version_control_modified
6086 }
6087 DiffHunkStatusKind::Deleted => {
6088 theme.colors().version_control_deleted
6089 }
6090 };
6091 ColoredRange {
6092 start: start_display_row,
6093 end: end_display_row,
6094 color,
6095 }
6096 });
6097
6098 marker_quads.extend(
6099 scrollbar_layout
6100 .marker_quads_for_ranges(marker_row_ranges, Some(0)),
6101 );
6102 }
6103
6104 for (background_highlight_id, (_, background_ranges)) in
6105 background_highlights.iter()
6106 {
6107 let is_search_highlights = *background_highlight_id
6108 == TypeId::of::<BufferSearchHighlights>();
6109 let is_text_highlights = *background_highlight_id
6110 == TypeId::of::<SelectedTextHighlight>();
6111 let is_symbol_occurrences = *background_highlight_id
6112 == TypeId::of::<DocumentHighlightRead>()
6113 || *background_highlight_id
6114 == TypeId::of::<DocumentHighlightWrite>();
6115 if (is_search_highlights && scrollbar_settings.search_results)
6116 || (is_text_highlights && scrollbar_settings.selected_text)
6117 || (is_symbol_occurrences && scrollbar_settings.selected_symbol)
6118 {
6119 let mut color = theme.status().info;
6120 if is_symbol_occurrences {
6121 color.fade_out(0.5);
6122 }
6123 let marker_row_ranges = background_ranges.iter().map(|range| {
6124 let display_start = range
6125 .start
6126 .to_display_point(&snapshot.display_snapshot);
6127 let display_end =
6128 range.end.to_display_point(&snapshot.display_snapshot);
6129 ColoredRange {
6130 start: display_start.row(),
6131 end: display_end.row(),
6132 color,
6133 }
6134 });
6135 marker_quads.extend(
6136 scrollbar_layout
6137 .marker_quads_for_ranges(marker_row_ranges, Some(1)),
6138 );
6139 }
6140 }
6141
6142 if scrollbar_settings.diagnostics != ScrollbarDiagnostics::None {
6143 let diagnostics = snapshot
6144 .buffer_snapshot
6145 .diagnostics_in_range::<Point>(Point::zero()..max_point)
6146 // Don't show diagnostics the user doesn't care about
6147 .filter(|diagnostic| {
6148 match (
6149 scrollbar_settings.diagnostics,
6150 diagnostic.diagnostic.severity,
6151 ) {
6152 (ScrollbarDiagnostics::All, _) => true,
6153 (
6154 ScrollbarDiagnostics::Error,
6155 lsp::DiagnosticSeverity::ERROR,
6156 ) => true,
6157 (
6158 ScrollbarDiagnostics::Warning,
6159 lsp::DiagnosticSeverity::ERROR
6160 | lsp::DiagnosticSeverity::WARNING,
6161 ) => true,
6162 (
6163 ScrollbarDiagnostics::Information,
6164 lsp::DiagnosticSeverity::ERROR
6165 | lsp::DiagnosticSeverity::WARNING
6166 | lsp::DiagnosticSeverity::INFORMATION,
6167 ) => true,
6168 (_, _) => false,
6169 }
6170 })
6171 // We want to sort by severity, in order to paint the most severe diagnostics last.
6172 .sorted_by_key(|diagnostic| {
6173 std::cmp::Reverse(diagnostic.diagnostic.severity)
6174 });
6175
6176 let marker_row_ranges = diagnostics.into_iter().map(|diagnostic| {
6177 let start_display = diagnostic
6178 .range
6179 .start
6180 .to_display_point(&snapshot.display_snapshot);
6181 let end_display = diagnostic
6182 .range
6183 .end
6184 .to_display_point(&snapshot.display_snapshot);
6185 let color = match diagnostic.diagnostic.severity {
6186 lsp::DiagnosticSeverity::ERROR => theme.status().error,
6187 lsp::DiagnosticSeverity::WARNING => theme.status().warning,
6188 lsp::DiagnosticSeverity::INFORMATION => theme.status().info,
6189 _ => theme.status().hint,
6190 };
6191 ColoredRange {
6192 start: start_display.row(),
6193 end: end_display.row(),
6194 color,
6195 }
6196 });
6197 marker_quads.extend(
6198 scrollbar_layout
6199 .marker_quads_for_ranges(marker_row_ranges, Some(2)),
6200 );
6201 }
6202
6203 Arc::from(marker_quads)
6204 })
6205 .await;
6206
6207 editor.update(cx, |editor, cx| {
6208 editor.scrollbar_marker_state.markers = scrollbar_markers;
6209 editor.scrollbar_marker_state.scrollbar_size = scrollbar_size;
6210 editor.scrollbar_marker_state.pending_refresh = None;
6211 cx.notify();
6212 })?;
6213
6214 Ok(())
6215 }));
6216 });
6217 }
6218
6219 fn paint_highlighted_range(
6220 &self,
6221 range: Range<DisplayPoint>,
6222 color: Hsla,
6223 corner_radius: Pixels,
6224 line_end_overshoot: Pixels,
6225 layout: &EditorLayout,
6226 window: &mut Window,
6227 ) {
6228 let start_row = layout.visible_display_row_range.start;
6229 let end_row = layout.visible_display_row_range.end;
6230 if range.start != range.end {
6231 let row_range = if range.end.column() == 0 {
6232 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
6233 } else {
6234 cmp::max(range.start.row(), start_row)
6235 ..cmp::min(range.end.row().next_row(), end_row)
6236 };
6237
6238 let highlighted_range = HighlightedRange {
6239 color,
6240 line_height: layout.position_map.line_height,
6241 corner_radius,
6242 start_y: layout.content_origin.y
6243 + row_range.start.as_f32() * layout.position_map.line_height
6244 - layout.position_map.scroll_pixel_position.y,
6245 lines: row_range
6246 .iter_rows()
6247 .map(|row| {
6248 let line_layout =
6249 &layout.position_map.line_layouts[row.minus(start_row) as usize];
6250 HighlightedRangeLine {
6251 start_x: if row == range.start.row() {
6252 layout.content_origin.x
6253 + line_layout.x_for_index(range.start.column() as usize)
6254 - layout.position_map.scroll_pixel_position.x
6255 } else {
6256 layout.content_origin.x
6257 - layout.position_map.scroll_pixel_position.x
6258 },
6259 end_x: if row == range.end.row() {
6260 layout.content_origin.x
6261 + line_layout.x_for_index(range.end.column() as usize)
6262 - layout.position_map.scroll_pixel_position.x
6263 } else {
6264 layout.content_origin.x + line_layout.width + line_end_overshoot
6265 - layout.position_map.scroll_pixel_position.x
6266 },
6267 }
6268 })
6269 .collect(),
6270 };
6271
6272 highlighted_range.paint(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 = (minimap_hitbox.size.height / layout.max_scroll_top)
6382 .min(layout.minimap_line_height);
6383
6384 let mut mouse_position = window.mouse_position();
6385
6386 window.on_mouse_event({
6387 let editor = self.editor.clone();
6388
6389 let minimap_hitbox = minimap_hitbox.clone();
6390
6391 move |event: &MouseMoveEvent, phase, window, cx| {
6392 if phase == DispatchPhase::Capture {
6393 return;
6394 }
6395
6396 editor.update(cx, |editor, cx| {
6397 if event.pressed_button == Some(MouseButton::Left)
6398 && editor.scroll_manager.is_dragging_minimap()
6399 {
6400 let old_position = mouse_position.along(minimap_axis);
6401 let new_position = event.position.along(minimap_axis);
6402 if (minimap_hitbox.origin.along(minimap_axis)
6403 ..minimap_hitbox.bottom_right().along(minimap_axis))
6404 .contains(&old_position)
6405 {
6406 let position =
6407 editor.scroll_position(cx).apply_along(minimap_axis, |p| {
6408 (p + (new_position - old_position) / pixels_per_line)
6409 .max(0.)
6410 });
6411 editor.set_scroll_position(position, window, cx);
6412 }
6413 cx.stop_propagation();
6414 } else {
6415 if minimap_hitbox.is_hovered(window) {
6416 editor.scroll_manager.set_is_hovering_minimap_thumb(
6417 !event.dragging()
6418 && layout
6419 .thumb_layout
6420 .thumb_bounds
6421 .is_some_and(|bounds| bounds.contains(&event.position)),
6422 cx,
6423 );
6424
6425 // Stop hover events from propagating to the
6426 // underlying editor if the minimap hitbox is hovered
6427 if !event.dragging() {
6428 cx.stop_propagation();
6429 }
6430 } else {
6431 editor.scroll_manager.hide_minimap_thumb(cx);
6432 }
6433 }
6434 mouse_position = event.position;
6435 });
6436 }
6437 });
6438
6439 if dragging_minimap {
6440 window.on_mouse_event({
6441 let editor = self.editor.clone();
6442 move |event: &MouseUpEvent, phase, window, cx| {
6443 if phase == DispatchPhase::Capture {
6444 return;
6445 }
6446
6447 editor.update(cx, |editor, cx| {
6448 if minimap_hitbox.is_hovered(window) {
6449 editor.scroll_manager.set_is_hovering_minimap_thumb(
6450 layout
6451 .thumb_layout
6452 .thumb_bounds
6453 .is_some_and(|bounds| bounds.contains(&event.position)),
6454 cx,
6455 );
6456 } else {
6457 editor.scroll_manager.hide_minimap_thumb(cx);
6458 }
6459 cx.stop_propagation();
6460 });
6461 }
6462 });
6463 } else {
6464 window.on_mouse_event({
6465 let editor = self.editor.clone();
6466
6467 move |event: &MouseDownEvent, phase, window, cx| {
6468 if phase == DispatchPhase::Capture || !minimap_hitbox.is_hovered(window) {
6469 return;
6470 }
6471
6472 let event_position = event.position;
6473
6474 let Some(thumb_bounds) = layout.thumb_layout.thumb_bounds else {
6475 return;
6476 };
6477
6478 editor.update(cx, |editor, cx| {
6479 if !thumb_bounds.contains(&event_position) {
6480 let click_position =
6481 event_position.relative_to(&minimap_hitbox.origin).y;
6482
6483 let top_position = (click_position
6484 - thumb_bounds.size.along(minimap_axis) / 2.0)
6485 .max(Pixels::ZERO);
6486
6487 let scroll_offset = (layout.minimap_scroll_top
6488 + top_position / layout.minimap_line_height)
6489 .min(layout.max_scroll_top);
6490
6491 let scroll_position = editor
6492 .scroll_position(cx)
6493 .apply_along(minimap_axis, |_| scroll_offset);
6494 editor.set_scroll_position(scroll_position, window, cx);
6495 }
6496
6497 editor.scroll_manager.set_is_dragging_minimap(cx);
6498 cx.stop_propagation();
6499 });
6500 }
6501 });
6502 }
6503 }
6504 }
6505
6506 fn paint_blocks(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
6507 for mut block in layout.blocks.drain(..) {
6508 if block.overlaps_gutter {
6509 block.element.paint(window, cx);
6510 } else {
6511 let mut bounds = layout.hitbox.bounds;
6512 bounds.origin.x += layout.gutter_hitbox.bounds.size.width;
6513 window.with_content_mask(Some(ContentMask { bounds }), |window| {
6514 block.element.paint(window, cx);
6515 })
6516 }
6517 }
6518 }
6519
6520 fn paint_inline_completion_popover(
6521 &mut self,
6522 layout: &mut EditorLayout,
6523 window: &mut Window,
6524 cx: &mut App,
6525 ) {
6526 if let Some(inline_completion_popover) = layout.inline_completion_popover.as_mut() {
6527 inline_completion_popover.paint(window, cx);
6528 }
6529 }
6530
6531 fn paint_mouse_context_menu(
6532 &mut self,
6533 layout: &mut EditorLayout,
6534 window: &mut Window,
6535 cx: &mut App,
6536 ) {
6537 if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() {
6538 mouse_context_menu.paint(window, cx);
6539 }
6540 }
6541
6542 fn paint_scroll_wheel_listener(
6543 &mut self,
6544 layout: &EditorLayout,
6545 window: &mut Window,
6546 cx: &mut App,
6547 ) {
6548 window.on_mouse_event({
6549 let position_map = layout.position_map.clone();
6550 let editor = self.editor.clone();
6551 let hitbox = layout.hitbox.clone();
6552 let mut delta = ScrollDelta::default();
6553
6554 // Set a minimum scroll_sensitivity of 0.01 to make sure the user doesn't
6555 // accidentally turn off their scrolling.
6556 let base_scroll_sensitivity =
6557 EditorSettings::get_global(cx).scroll_sensitivity.max(0.01);
6558
6559 // Use a minimum fast_scroll_sensitivity for same reason above
6560 let fast_scroll_sensitivity = EditorSettings::get_global(cx)
6561 .fast_scroll_sensitivity
6562 .max(0.01);
6563
6564 move |event: &ScrollWheelEvent, phase, window, cx| {
6565 let scroll_sensitivity = {
6566 if event.modifiers.alt {
6567 fast_scroll_sensitivity
6568 } else {
6569 base_scroll_sensitivity
6570 }
6571 };
6572
6573 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
6574 delta = delta.coalesce(event.delta);
6575 editor.update(cx, |editor, cx| {
6576 let position_map: &PositionMap = &position_map;
6577
6578 let line_height = position_map.line_height;
6579 let max_glyph_width = position_map.em_width;
6580 let (delta, axis) = match delta {
6581 gpui::ScrollDelta::Pixels(mut pixels) => {
6582 //Trackpad
6583 let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
6584 (pixels, axis)
6585 }
6586
6587 gpui::ScrollDelta::Lines(lines) => {
6588 //Not trackpad
6589 let pixels =
6590 point(lines.x * max_glyph_width, lines.y * line_height);
6591 (pixels, None)
6592 }
6593 };
6594
6595 let current_scroll_position = position_map.snapshot.scroll_position();
6596 let x = (current_scroll_position.x * max_glyph_width
6597 - (delta.x * scroll_sensitivity))
6598 / max_glyph_width;
6599 let y = (current_scroll_position.y * line_height
6600 - (delta.y * scroll_sensitivity))
6601 / line_height;
6602 let mut scroll_position =
6603 point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
6604 let forbid_vertical_scroll = editor.scroll_manager.forbid_vertical_scroll();
6605 if forbid_vertical_scroll {
6606 scroll_position.y = current_scroll_position.y;
6607 }
6608
6609 if scroll_position != current_scroll_position {
6610 editor.scroll(scroll_position, axis, window, cx);
6611 cx.stop_propagation();
6612 } else if y < 0. {
6613 // Due to clamping, we may fail to detect cases of overscroll to the top;
6614 // We want the scroll manager to get an update in such cases and detect the change of direction
6615 // on the next frame.
6616 cx.notify();
6617 }
6618 });
6619 }
6620 }
6621 });
6622 }
6623
6624 fn paint_mouse_listeners(&mut self, layout: &EditorLayout, window: &mut Window, cx: &mut App) {
6625 if self.editor.read(cx).mode.is_minimap() {
6626 return;
6627 }
6628
6629 self.paint_scroll_wheel_listener(layout, window, cx);
6630
6631 window.on_mouse_event({
6632 let position_map = layout.position_map.clone();
6633 let editor = self.editor.clone();
6634 let diff_hunk_range =
6635 layout
6636 .display_hunks
6637 .iter()
6638 .find_map(|(hunk, hunk_hitbox)| match hunk {
6639 DisplayDiffHunk::Folded { .. } => None,
6640 DisplayDiffHunk::Unfolded {
6641 multi_buffer_range, ..
6642 } => {
6643 if hunk_hitbox
6644 .as_ref()
6645 .map(|hitbox| hitbox.is_hovered(window))
6646 .unwrap_or(false)
6647 {
6648 Some(multi_buffer_range.clone())
6649 } else {
6650 None
6651 }
6652 }
6653 });
6654 let line_numbers = layout.line_numbers.clone();
6655
6656 move |event: &MouseDownEvent, phase, window, cx| {
6657 if phase == DispatchPhase::Bubble {
6658 match event.button {
6659 MouseButton::Left => editor.update(cx, |editor, cx| {
6660 let pending_mouse_down = editor
6661 .pending_mouse_down
6662 .get_or_insert_with(Default::default)
6663 .clone();
6664
6665 *pending_mouse_down.borrow_mut() = Some(event.clone());
6666
6667 Self::mouse_left_down(
6668 editor,
6669 event,
6670 diff_hunk_range.clone(),
6671 &position_map,
6672 line_numbers.as_ref(),
6673 window,
6674 cx,
6675 );
6676 }),
6677 MouseButton::Right => editor.update(cx, |editor, cx| {
6678 Self::mouse_right_down(editor, event, &position_map, window, cx);
6679 }),
6680 MouseButton::Middle => editor.update(cx, |editor, cx| {
6681 Self::mouse_middle_down(editor, event, &position_map, window, cx);
6682 }),
6683 _ => {}
6684 };
6685 }
6686 }
6687 });
6688
6689 window.on_mouse_event({
6690 let editor = self.editor.clone();
6691 let position_map = layout.position_map.clone();
6692
6693 move |event: &MouseUpEvent, phase, window, cx| {
6694 if phase == DispatchPhase::Bubble {
6695 editor.update(cx, |editor, cx| {
6696 Self::mouse_up(editor, event, &position_map, window, cx)
6697 });
6698 }
6699 }
6700 });
6701
6702 window.on_mouse_event({
6703 let editor = self.editor.clone();
6704 let position_map = layout.position_map.clone();
6705 let mut captured_mouse_down = None;
6706
6707 move |event: &MouseUpEvent, phase, window, cx| match phase {
6708 // Clear the pending mouse down during the capture phase,
6709 // so that it happens even if another event handler stops
6710 // propagation.
6711 DispatchPhase::Capture => editor.update(cx, |editor, _cx| {
6712 let pending_mouse_down = editor
6713 .pending_mouse_down
6714 .get_or_insert_with(Default::default)
6715 .clone();
6716
6717 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
6718 if pending_mouse_down.is_some() && position_map.text_hitbox.is_hovered(window) {
6719 captured_mouse_down = pending_mouse_down.take();
6720 window.refresh();
6721 }
6722 }),
6723 // Fire click handlers during the bubble phase.
6724 DispatchPhase::Bubble => editor.update(cx, |editor, cx| {
6725 if let Some(mouse_down) = captured_mouse_down.take() {
6726 let event = ClickEvent {
6727 down: mouse_down,
6728 up: event.clone(),
6729 };
6730 Self::click(editor, &event, &position_map, window, cx);
6731 }
6732 }),
6733 }
6734 });
6735
6736 window.on_mouse_event({
6737 let position_map = layout.position_map.clone();
6738 let editor = self.editor.clone();
6739
6740 move |event: &MouseMoveEvent, phase, window, cx| {
6741 if phase == DispatchPhase::Bubble {
6742 editor.update(cx, |editor, cx| {
6743 if editor.hover_state.focused(window, cx) {
6744 return;
6745 }
6746 if event.pressed_button == Some(MouseButton::Left)
6747 || event.pressed_button == Some(MouseButton::Middle)
6748 {
6749 Self::mouse_dragged(editor, event, &position_map, window, cx)
6750 }
6751
6752 Self::mouse_moved(editor, event, &position_map, window, cx)
6753 });
6754 }
6755 }
6756 });
6757 }
6758
6759 fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
6760 bounds.top_right().x - self.style.scrollbar_width
6761 }
6762
6763 fn column_pixels(&self, column: usize, window: &mut Window, _: &mut App) -> Pixels {
6764 let style = &self.style;
6765 let font_size = style.text.font_size.to_pixels(window.rem_size());
6766 let layout = window.text_system().shape_line(
6767 SharedString::from(" ".repeat(column)),
6768 font_size,
6769 &[TextRun {
6770 len: column,
6771 font: style.text.font(),
6772 color: Hsla::default(),
6773 background_color: None,
6774 underline: None,
6775 strikethrough: None,
6776 }],
6777 );
6778
6779 layout.width
6780 }
6781
6782 fn max_line_number_width(
6783 &self,
6784 snapshot: &EditorSnapshot,
6785 window: &mut Window,
6786 cx: &mut App,
6787 ) -> Pixels {
6788 let digit_count = snapshot.widest_line_number().ilog10() + 1;
6789 self.column_pixels(digit_count as usize, window, cx)
6790 }
6791
6792 fn shape_line_number(
6793 &self,
6794 text: SharedString,
6795 color: Hsla,
6796 window: &mut Window,
6797 ) -> ShapedLine {
6798 let run = TextRun {
6799 len: text.len(),
6800 font: self.style.text.font(),
6801 color,
6802 background_color: None,
6803 underline: None,
6804 strikethrough: None,
6805 };
6806 window.text_system().shape_line(
6807 text,
6808 self.style.text.font_size.to_pixels(window.rem_size()),
6809 &[run],
6810 )
6811 }
6812
6813 fn diff_hunk_hollow(status: DiffHunkStatus, cx: &mut App) -> bool {
6814 let unstaged = status.has_secondary_hunk();
6815 let unstaged_hollow = ProjectSettings::get_global(cx)
6816 .git
6817 .hunk_style
6818 .map_or(false, |style| {
6819 matches!(style, GitHunkStyleSetting::UnstagedHollow)
6820 });
6821
6822 unstaged == unstaged_hollow
6823 }
6824}
6825
6826fn header_jump_data(
6827 snapshot: &EditorSnapshot,
6828 block_row_start: DisplayRow,
6829 height: u32,
6830 for_excerpt: &ExcerptInfo,
6831) -> JumpData {
6832 let range = &for_excerpt.range;
6833 let buffer = &for_excerpt.buffer;
6834 let jump_anchor = range.primary.start;
6835
6836 let excerpt_start = range.context.start;
6837 let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
6838 let rows_from_excerpt_start = if jump_anchor == excerpt_start {
6839 0
6840 } else {
6841 let excerpt_start_point = language::ToPoint::to_point(&excerpt_start, buffer);
6842 jump_position.row.saturating_sub(excerpt_start_point.row)
6843 };
6844
6845 let line_offset_from_top = (block_row_start.0 + height + rows_from_excerpt_start)
6846 .saturating_sub(
6847 snapshot
6848 .scroll_anchor
6849 .scroll_position(&snapshot.display_snapshot)
6850 .y as u32,
6851 );
6852
6853 JumpData::MultiBufferPoint {
6854 excerpt_id: for_excerpt.id,
6855 anchor: jump_anchor,
6856 position: jump_position,
6857 line_offset_from_top,
6858 }
6859}
6860
6861pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);
6862
6863impl AcceptEditPredictionBinding {
6864 pub fn keystroke(&self) -> Option<&Keystroke> {
6865 if let Some(binding) = self.0.as_ref() {
6866 match &binding.keystrokes() {
6867 [keystroke, ..] => Some(keystroke),
6868 _ => None,
6869 }
6870 } else {
6871 None
6872 }
6873 }
6874}
6875
6876fn prepaint_gutter_button(
6877 button: IconButton,
6878 row: DisplayRow,
6879 line_height: Pixels,
6880 gutter_dimensions: &GutterDimensions,
6881 scroll_pixel_position: gpui::Point<Pixels>,
6882 gutter_hitbox: &Hitbox,
6883 display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
6884 window: &mut Window,
6885 cx: &mut App,
6886) -> AnyElement {
6887 let mut button = button.into_any_element();
6888
6889 let available_space = size(
6890 AvailableSpace::MinContent,
6891 AvailableSpace::Definite(line_height),
6892 );
6893 let indicator_size = button.layout_as_root(available_space, window, cx);
6894
6895 let blame_width = gutter_dimensions.git_blame_entries_width;
6896 let gutter_width = display_hunks
6897 .binary_search_by(|(hunk, _)| match hunk {
6898 DisplayDiffHunk::Folded { display_row } => display_row.cmp(&row),
6899 DisplayDiffHunk::Unfolded {
6900 display_row_range, ..
6901 } => {
6902 if display_row_range.end <= row {
6903 Ordering::Less
6904 } else if display_row_range.start > row {
6905 Ordering::Greater
6906 } else {
6907 Ordering::Equal
6908 }
6909 }
6910 })
6911 .ok()
6912 .and_then(|ix| Some(display_hunks[ix].1.as_ref()?.size.width));
6913 let left_offset = blame_width.max(gutter_width).unwrap_or_default();
6914
6915 let mut x = left_offset;
6916 let available_width = gutter_dimensions.margin + gutter_dimensions.left_padding
6917 - indicator_size.width
6918 - left_offset;
6919 x += available_width / 2.;
6920
6921 let mut y = row.as_f32() * line_height - scroll_pixel_position.y;
6922 y += (line_height - indicator_size.height) / 2.;
6923
6924 button.prepaint_as_root(
6925 gutter_hitbox.origin + point(x, y),
6926 available_space,
6927 window,
6928 cx,
6929 );
6930 button
6931}
6932
6933fn render_inline_blame_entry(
6934 blame_entry: BlameEntry,
6935 style: &EditorStyle,
6936 cx: &mut App,
6937) -> Option<AnyElement> {
6938 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
6939 renderer.render_inline_blame_entry(&style.text, blame_entry, cx)
6940}
6941
6942fn render_blame_entry_popover(
6943 blame_entry: BlameEntry,
6944 scroll_handle: ScrollHandle,
6945 commit_message: Option<ParsedCommitMessage>,
6946 markdown: Entity<Markdown>,
6947 workspace: WeakEntity<Workspace>,
6948 blame: &Entity<GitBlame>,
6949 window: &mut Window,
6950 cx: &mut App,
6951) -> Option<AnyElement> {
6952 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
6953 let blame = blame.read(cx);
6954 let repository = blame.repository(cx)?.clone();
6955 renderer.render_blame_entry_popover(
6956 blame_entry,
6957 scroll_handle,
6958 commit_message,
6959 markdown,
6960 repository,
6961 workspace,
6962 window,
6963 cx,
6964 )
6965}
6966
6967fn render_blame_entry(
6968 ix: usize,
6969 blame: &Entity<GitBlame>,
6970 blame_entry: BlameEntry,
6971 style: &EditorStyle,
6972 last_used_color: &mut Option<(PlayerColor, Oid)>,
6973 editor: Entity<Editor>,
6974 workspace: Entity<Workspace>,
6975 renderer: Arc<dyn BlameRenderer>,
6976 cx: &mut App,
6977) -> Option<AnyElement> {
6978 let mut sha_color = cx
6979 .theme()
6980 .players()
6981 .color_for_participant(blame_entry.sha.into());
6982
6983 // If the last color we used is the same as the one we get for this line, but
6984 // the commit SHAs are different, then we try again to get a different color.
6985 match *last_used_color {
6986 Some((color, sha)) if sha != blame_entry.sha && color.cursor == sha_color.cursor => {
6987 let index: u32 = blame_entry.sha.into();
6988 sha_color = cx.theme().players().color_for_participant(index + 1);
6989 }
6990 _ => {}
6991 };
6992 last_used_color.replace((sha_color, blame_entry.sha));
6993
6994 let blame = blame.read(cx);
6995 let details = blame.details_for_entry(&blame_entry);
6996 let repository = blame.repository(cx)?;
6997 renderer.render_blame_entry(
6998 &style.text,
6999 blame_entry,
7000 details,
7001 repository,
7002 workspace.downgrade(),
7003 editor,
7004 ix,
7005 sha_color.cursor,
7006 cx,
7007 )
7008}
7009
7010#[derive(Debug)]
7011pub(crate) struct LineWithInvisibles {
7012 fragments: SmallVec<[LineFragment; 1]>,
7013 invisibles: Vec<Invisible>,
7014 len: usize,
7015 pub(crate) width: Pixels,
7016 font_size: Pixels,
7017}
7018
7019enum LineFragment {
7020 Text(ShapedLine),
7021 Element {
7022 id: FoldId,
7023 element: Option<AnyElement>,
7024 size: Size<Pixels>,
7025 len: usize,
7026 },
7027}
7028
7029impl fmt::Debug for LineFragment {
7030 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
7031 match self {
7032 LineFragment::Text(shaped_line) => f.debug_tuple("Text").field(shaped_line).finish(),
7033 LineFragment::Element { size, len, .. } => f
7034 .debug_struct("Element")
7035 .field("size", size)
7036 .field("len", len)
7037 .finish(),
7038 }
7039 }
7040}
7041
7042impl LineWithInvisibles {
7043 fn from_chunks<'a>(
7044 chunks: impl Iterator<Item = HighlightedChunk<'a>>,
7045 editor_style: &EditorStyle,
7046 max_line_len: usize,
7047 max_line_count: usize,
7048 editor_mode: &EditorMode,
7049 text_width: Pixels,
7050 is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
7051 window: &mut Window,
7052 cx: &mut App,
7053 ) -> Vec<Self> {
7054 let text_style = &editor_style.text;
7055 let mut layouts = Vec::with_capacity(max_line_count);
7056 let mut fragments: SmallVec<[LineFragment; 1]> = SmallVec::new();
7057 let mut line = String::new();
7058 let mut invisibles = Vec::new();
7059 let mut width = Pixels::ZERO;
7060 let mut len = 0;
7061 let mut styles = Vec::new();
7062 let mut non_whitespace_added = false;
7063 let mut row = 0;
7064 let mut line_exceeded_max_len = false;
7065 let font_size = text_style.font_size.to_pixels(window.rem_size());
7066
7067 let ellipsis = SharedString::from("⋯");
7068
7069 for highlighted_chunk in chunks.chain([HighlightedChunk {
7070 text: "\n",
7071 style: None,
7072 is_tab: false,
7073 is_inlay: false,
7074 replacement: None,
7075 }]) {
7076 if let Some(replacement) = highlighted_chunk.replacement {
7077 if !line.is_empty() {
7078 let shaped_line =
7079 window
7080 .text_system()
7081 .shape_line(line.clone().into(), font_size, &styles);
7082 width += shaped_line.width;
7083 len += shaped_line.len;
7084 fragments.push(LineFragment::Text(shaped_line));
7085 line.clear();
7086 styles.clear();
7087 }
7088
7089 match replacement {
7090 ChunkReplacement::Renderer(renderer) => {
7091 let available_width = if renderer.constrain_width {
7092 let chunk = if highlighted_chunk.text == ellipsis.as_ref() {
7093 ellipsis.clone()
7094 } else {
7095 SharedString::from(Arc::from(highlighted_chunk.text))
7096 };
7097 let shaped_line = window.text_system().shape_line(
7098 chunk,
7099 font_size,
7100 &[text_style.to_run(highlighted_chunk.text.len())],
7101 );
7102 AvailableSpace::Definite(shaped_line.width)
7103 } else {
7104 AvailableSpace::MinContent
7105 };
7106
7107 let mut element = (renderer.render)(&mut ChunkRendererContext {
7108 context: cx,
7109 window,
7110 max_width: text_width,
7111 });
7112 let line_height = text_style.line_height_in_pixels(window.rem_size());
7113 let size = element.layout_as_root(
7114 size(available_width, AvailableSpace::Definite(line_height)),
7115 window,
7116 cx,
7117 );
7118
7119 width += size.width;
7120 len += highlighted_chunk.text.len();
7121 fragments.push(LineFragment::Element {
7122 id: renderer.id,
7123 element: Some(element),
7124 size,
7125 len: highlighted_chunk.text.len(),
7126 });
7127 }
7128 ChunkReplacement::Str(x) => {
7129 let text_style = if let Some(style) = highlighted_chunk.style {
7130 Cow::Owned(text_style.clone().highlight(style))
7131 } else {
7132 Cow::Borrowed(text_style)
7133 };
7134
7135 let run = TextRun {
7136 len: x.len(),
7137 font: text_style.font(),
7138 color: text_style.color,
7139 background_color: text_style.background_color,
7140 underline: text_style.underline,
7141 strikethrough: text_style.strikethrough,
7142 };
7143 let line_layout = window
7144 .text_system()
7145 .shape_line(x, font_size, &[run])
7146 .with_len(highlighted_chunk.text.len());
7147
7148 width += line_layout.width;
7149 len += highlighted_chunk.text.len();
7150 fragments.push(LineFragment::Text(line_layout))
7151 }
7152 }
7153 } else {
7154 for (ix, mut line_chunk) in highlighted_chunk.text.split('\n').enumerate() {
7155 if ix > 0 {
7156 let shaped_line = window.text_system().shape_line(
7157 line.clone().into(),
7158 font_size,
7159 &styles,
7160 );
7161 width += shaped_line.width;
7162 len += shaped_line.len;
7163 fragments.push(LineFragment::Text(shaped_line));
7164 layouts.push(Self {
7165 width: mem::take(&mut width),
7166 len: mem::take(&mut len),
7167 fragments: mem::take(&mut fragments),
7168 invisibles: std::mem::take(&mut invisibles),
7169 font_size,
7170 });
7171
7172 line.clear();
7173 styles.clear();
7174 row += 1;
7175 line_exceeded_max_len = false;
7176 non_whitespace_added = false;
7177 if row == max_line_count {
7178 return layouts;
7179 }
7180 }
7181
7182 if !line_chunk.is_empty() && !line_exceeded_max_len {
7183 let text_style = if let Some(style) = highlighted_chunk.style {
7184 Cow::Owned(text_style.clone().highlight(style))
7185 } else {
7186 Cow::Borrowed(text_style)
7187 };
7188
7189 if line.len() + line_chunk.len() > max_line_len {
7190 let mut chunk_len = max_line_len - line.len();
7191 while !line_chunk.is_char_boundary(chunk_len) {
7192 chunk_len -= 1;
7193 }
7194 line_chunk = &line_chunk[..chunk_len];
7195 line_exceeded_max_len = true;
7196 }
7197
7198 styles.push(TextRun {
7199 len: line_chunk.len(),
7200 font: text_style.font(),
7201 color: text_style.color,
7202 background_color: text_style.background_color,
7203 underline: text_style.underline,
7204 strikethrough: text_style.strikethrough,
7205 });
7206
7207 if editor_mode.is_full() && !highlighted_chunk.is_inlay {
7208 // Line wrap pads its contents with fake whitespaces,
7209 // avoid printing them
7210 let is_soft_wrapped = is_row_soft_wrapped(row);
7211 if highlighted_chunk.is_tab {
7212 if non_whitespace_added || !is_soft_wrapped {
7213 invisibles.push(Invisible::Tab {
7214 line_start_offset: line.len(),
7215 line_end_offset: line.len() + line_chunk.len(),
7216 });
7217 }
7218 } else {
7219 invisibles.extend(line_chunk.char_indices().filter_map(
7220 |(index, c)| {
7221 let is_whitespace = c.is_whitespace();
7222 non_whitespace_added |= !is_whitespace;
7223 if is_whitespace
7224 && (non_whitespace_added || !is_soft_wrapped)
7225 {
7226 Some(Invisible::Whitespace {
7227 line_offset: line.len() + index,
7228 })
7229 } else {
7230 None
7231 }
7232 },
7233 ))
7234 }
7235 }
7236
7237 line.push_str(line_chunk);
7238 }
7239 }
7240 }
7241 }
7242
7243 layouts
7244 }
7245
7246 fn prepaint(
7247 &mut self,
7248 line_height: Pixels,
7249 scroll_pixel_position: gpui::Point<Pixels>,
7250 row: DisplayRow,
7251 content_origin: gpui::Point<Pixels>,
7252 line_elements: &mut SmallVec<[AnyElement; 1]>,
7253 window: &mut Window,
7254 cx: &mut App,
7255 ) {
7256 let line_y = line_height * (row.as_f32() - scroll_pixel_position.y / line_height);
7257 let mut fragment_origin = content_origin + gpui::point(-scroll_pixel_position.x, line_y);
7258 for fragment in &mut self.fragments {
7259 match fragment {
7260 LineFragment::Text(line) => {
7261 fragment_origin.x += line.width;
7262 }
7263 LineFragment::Element { element, size, .. } => {
7264 let mut element = element
7265 .take()
7266 .expect("you can't prepaint LineWithInvisibles twice");
7267
7268 // Center the element vertically within the line.
7269 let mut element_origin = fragment_origin;
7270 element_origin.y += (line_height - size.height) / 2.;
7271 element.prepaint_at(element_origin, window, cx);
7272 line_elements.push(element);
7273
7274 fragment_origin.x += size.width;
7275 }
7276 }
7277 }
7278 }
7279
7280 fn draw(
7281 &self,
7282 layout: &EditorLayout,
7283 row: DisplayRow,
7284 content_origin: gpui::Point<Pixels>,
7285 whitespace_setting: ShowWhitespaceSetting,
7286 selection_ranges: &[Range<DisplayPoint>],
7287 window: &mut Window,
7288 cx: &mut App,
7289 ) {
7290 let line_height = layout.position_map.line_height;
7291 let line_y = line_height
7292 * (row.as_f32() - layout.position_map.scroll_pixel_position.y / line_height);
7293
7294 let mut fragment_origin =
7295 content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y);
7296
7297 for fragment in &self.fragments {
7298 match fragment {
7299 LineFragment::Text(line) => {
7300 line.paint(fragment_origin, line_height, window, cx)
7301 .log_err();
7302 fragment_origin.x += line.width;
7303 }
7304 LineFragment::Element { size, .. } => {
7305 fragment_origin.x += size.width;
7306 }
7307 }
7308 }
7309
7310 self.draw_invisibles(
7311 selection_ranges,
7312 layout,
7313 content_origin,
7314 line_y,
7315 row,
7316 line_height,
7317 whitespace_setting,
7318 window,
7319 cx,
7320 );
7321 }
7322
7323 fn draw_background(
7324 &self,
7325 layout: &EditorLayout,
7326 row: DisplayRow,
7327 content_origin: gpui::Point<Pixels>,
7328 window: &mut Window,
7329 cx: &mut App,
7330 ) {
7331 let line_height = layout.position_map.line_height;
7332 let line_y = line_height
7333 * (row.as_f32() - layout.position_map.scroll_pixel_position.y / line_height);
7334
7335 let mut fragment_origin =
7336 content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y);
7337
7338 for fragment in &self.fragments {
7339 match fragment {
7340 LineFragment::Text(line) => {
7341 line.paint_background(fragment_origin, line_height, window, cx)
7342 .log_err();
7343 fragment_origin.x += line.width;
7344 }
7345 LineFragment::Element { size, .. } => {
7346 fragment_origin.x += size.width;
7347 }
7348 }
7349 }
7350 }
7351
7352 fn draw_invisibles(
7353 &self,
7354 selection_ranges: &[Range<DisplayPoint>],
7355 layout: &EditorLayout,
7356 content_origin: gpui::Point<Pixels>,
7357 line_y: Pixels,
7358 row: DisplayRow,
7359 line_height: Pixels,
7360 whitespace_setting: ShowWhitespaceSetting,
7361 window: &mut Window,
7362 cx: &mut App,
7363 ) {
7364 let extract_whitespace_info = |invisible: &Invisible| {
7365 let (token_offset, token_end_offset, invisible_symbol) = match invisible {
7366 Invisible::Tab {
7367 line_start_offset,
7368 line_end_offset,
7369 } => (*line_start_offset, *line_end_offset, &layout.tab_invisible),
7370 Invisible::Whitespace { line_offset } => {
7371 (*line_offset, line_offset + 1, &layout.space_invisible)
7372 }
7373 };
7374
7375 let x_offset = self.x_for_index(token_offset);
7376 let invisible_offset =
7377 (layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
7378 let origin = content_origin
7379 + gpui::point(
7380 x_offset + invisible_offset - layout.position_map.scroll_pixel_position.x,
7381 line_y,
7382 );
7383
7384 (
7385 [token_offset, token_end_offset],
7386 Box::new(move |window: &mut Window, cx: &mut App| {
7387 invisible_symbol
7388 .paint(origin, line_height, window, cx)
7389 .log_err();
7390 }),
7391 )
7392 };
7393
7394 let invisible_iter = self.invisibles.iter().map(extract_whitespace_info);
7395 match whitespace_setting {
7396 ShowWhitespaceSetting::None => (),
7397 ShowWhitespaceSetting::All => invisible_iter.for_each(|(_, paint)| paint(window, cx)),
7398 ShowWhitespaceSetting::Selection => invisible_iter.for_each(|([start, _], paint)| {
7399 let invisible_point = DisplayPoint::new(row, start as u32);
7400 if !selection_ranges
7401 .iter()
7402 .any(|region| region.start <= invisible_point && invisible_point < region.end)
7403 {
7404 return;
7405 }
7406
7407 paint(window, cx);
7408 }),
7409
7410 ShowWhitespaceSetting::Trailing => {
7411 let mut previous_start = self.len;
7412 for ([start, end], paint) in invisible_iter.rev() {
7413 if previous_start != end {
7414 break;
7415 }
7416 previous_start = start;
7417 paint(window, cx);
7418 }
7419 }
7420
7421 // For a whitespace to be on a boundary, any of the following conditions need to be met:
7422 // - It is a tab
7423 // - It is adjacent to an edge (start or end)
7424 // - It is adjacent to a whitespace (left or right)
7425 ShowWhitespaceSetting::Boundary => {
7426 // 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
7427 // the above cases.
7428 // Note: We zip in the original `invisibles` to check for tab equality
7429 let mut last_seen: Option<(bool, usize, Box<dyn Fn(&mut Window, &mut App)>)> = None;
7430 for (([start, end], paint), invisible) in
7431 invisible_iter.zip_eq(self.invisibles.iter())
7432 {
7433 let should_render = match (&last_seen, invisible) {
7434 (_, Invisible::Tab { .. }) => true,
7435 (Some((_, last_end, _)), _) => *last_end == start,
7436 _ => false,
7437 };
7438
7439 if should_render || start == 0 || end == self.len {
7440 paint(window, cx);
7441
7442 // Since we are scanning from the left, we will skip over the first available whitespace that is part
7443 // of a boundary between non-whitespace segments, so we correct by manually redrawing it if needed.
7444 if let Some((should_render_last, last_end, paint_last)) = last_seen {
7445 // Note that we need to make sure that the last one is actually adjacent
7446 if !should_render_last && last_end == start {
7447 paint_last(window, cx);
7448 }
7449 }
7450 }
7451
7452 // Manually render anything within a selection
7453 let invisible_point = DisplayPoint::new(row, start as u32);
7454 if selection_ranges.iter().any(|region| {
7455 region.start <= invisible_point && invisible_point < region.end
7456 }) {
7457 paint(window, cx);
7458 }
7459
7460 last_seen = Some((should_render, end, paint));
7461 }
7462 }
7463 }
7464 }
7465
7466 pub fn x_for_index(&self, index: usize) -> Pixels {
7467 let mut fragment_start_x = Pixels::ZERO;
7468 let mut fragment_start_index = 0;
7469
7470 for fragment in &self.fragments {
7471 match fragment {
7472 LineFragment::Text(shaped_line) => {
7473 let fragment_end_index = fragment_start_index + shaped_line.len;
7474 if index < fragment_end_index {
7475 return fragment_start_x
7476 + shaped_line.x_for_index(index - fragment_start_index);
7477 }
7478 fragment_start_x += shaped_line.width;
7479 fragment_start_index = fragment_end_index;
7480 }
7481 LineFragment::Element { len, size, .. } => {
7482 let fragment_end_index = fragment_start_index + len;
7483 if index < fragment_end_index {
7484 return fragment_start_x;
7485 }
7486 fragment_start_x += size.width;
7487 fragment_start_index = fragment_end_index;
7488 }
7489 }
7490 }
7491
7492 fragment_start_x
7493 }
7494
7495 pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
7496 let mut fragment_start_x = Pixels::ZERO;
7497 let mut fragment_start_index = 0;
7498
7499 for fragment in &self.fragments {
7500 match fragment {
7501 LineFragment::Text(shaped_line) => {
7502 let fragment_end_x = fragment_start_x + shaped_line.width;
7503 if x < fragment_end_x {
7504 return Some(
7505 fragment_start_index + shaped_line.index_for_x(x - fragment_start_x)?,
7506 );
7507 }
7508 fragment_start_x = fragment_end_x;
7509 fragment_start_index += shaped_line.len;
7510 }
7511 LineFragment::Element { len, size, .. } => {
7512 let fragment_end_x = fragment_start_x + size.width;
7513 if x < fragment_end_x {
7514 return Some(fragment_start_index);
7515 }
7516 fragment_start_index += len;
7517 fragment_start_x = fragment_end_x;
7518 }
7519 }
7520 }
7521
7522 None
7523 }
7524
7525 pub fn font_id_for_index(&self, index: usize) -> Option<FontId> {
7526 let mut fragment_start_index = 0;
7527
7528 for fragment in &self.fragments {
7529 match fragment {
7530 LineFragment::Text(shaped_line) => {
7531 let fragment_end_index = fragment_start_index + shaped_line.len;
7532 if index < fragment_end_index {
7533 return shaped_line.font_id_for_index(index - fragment_start_index);
7534 }
7535 fragment_start_index = fragment_end_index;
7536 }
7537 LineFragment::Element { len, .. } => {
7538 let fragment_end_index = fragment_start_index + len;
7539 if index < fragment_end_index {
7540 return None;
7541 }
7542 fragment_start_index = fragment_end_index;
7543 }
7544 }
7545 }
7546
7547 None
7548 }
7549}
7550
7551#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7552enum Invisible {
7553 /// A tab character
7554 ///
7555 /// A tab character is internally represented by spaces (configured by the user's tab width)
7556 /// aligned to the nearest column, so it's necessary to store the start and end offset for
7557 /// adjacency checks.
7558 Tab {
7559 line_start_offset: usize,
7560 line_end_offset: usize,
7561 },
7562 Whitespace {
7563 line_offset: usize,
7564 },
7565}
7566
7567impl EditorElement {
7568 /// Returns the rem size to use when rendering the [`EditorElement`].
7569 ///
7570 /// This allows UI elements to scale based on the `buffer_font_size`.
7571 fn rem_size(&self, cx: &mut App) -> Option<Pixels> {
7572 match self.editor.read(cx).mode {
7573 EditorMode::Full {
7574 scale_ui_elements_with_buffer_font_size: true,
7575 ..
7576 }
7577 | EditorMode::Minimap { .. } => {
7578 let buffer_font_size = self.style.text.font_size;
7579 match buffer_font_size {
7580 AbsoluteLength::Pixels(pixels) => {
7581 let rem_size_scale = {
7582 // Our default UI font size is 14px on a 16px base scale.
7583 // This means the default UI font size is 0.875rems.
7584 let default_font_size_scale = 14. / ui::BASE_REM_SIZE_IN_PX;
7585
7586 // We then determine the delta between a single rem and the default font
7587 // size scale.
7588 let default_font_size_delta = 1. - default_font_size_scale;
7589
7590 // Finally, we add this delta to 1rem to get the scale factor that
7591 // should be used to scale up the UI.
7592 1. + default_font_size_delta
7593 };
7594
7595 Some(pixels * rem_size_scale)
7596 }
7597 AbsoluteLength::Rems(rems) => {
7598 Some(rems.to_pixels(ui::BASE_REM_SIZE_IN_PX.into()))
7599 }
7600 }
7601 }
7602 // We currently use single-line and auto-height editors in UI contexts,
7603 // so we don't want to scale everything with the buffer font size, as it
7604 // ends up looking off.
7605 _ => None,
7606 }
7607 }
7608
7609 fn editor_with_selections(&self, cx: &App) -> Option<Entity<Editor>> {
7610 if let EditorMode::Minimap { parent } = self.editor.read(cx).mode() {
7611 parent.upgrade()
7612 } else {
7613 Some(self.editor.clone())
7614 }
7615 }
7616}
7617
7618impl Element for EditorElement {
7619 type RequestLayoutState = ();
7620 type PrepaintState = EditorLayout;
7621
7622 fn id(&self) -> Option<ElementId> {
7623 None
7624 }
7625
7626 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
7627 None
7628 }
7629
7630 fn request_layout(
7631 &mut self,
7632 _: Option<&GlobalElementId>,
7633 __inspector_id: Option<&gpui::InspectorElementId>,
7634 window: &mut Window,
7635 cx: &mut App,
7636 ) -> (gpui::LayoutId, ()) {
7637 let rem_size = self.rem_size(cx);
7638 window.with_rem_size(rem_size, |window| {
7639 self.editor.update(cx, |editor, cx| {
7640 editor.set_style(self.style.clone(), window, cx);
7641
7642 let layout_id = match editor.mode {
7643 EditorMode::SingleLine { auto_width } => {
7644 let rem_size = window.rem_size();
7645
7646 let height = self.style.text.line_height_in_pixels(rem_size);
7647 if auto_width {
7648 let editor_handle = cx.entity().clone();
7649 let style = self.style.clone();
7650 window.request_measured_layout(
7651 Style::default(),
7652 move |_, _, window, cx| {
7653 let editor_snapshot = editor_handle
7654 .update(cx, |editor, cx| editor.snapshot(window, cx));
7655 let line = Self::layout_lines(
7656 DisplayRow(0)..DisplayRow(1),
7657 &editor_snapshot,
7658 &style,
7659 px(f32::MAX),
7660 |_| false, // Single lines never soft wrap
7661 window,
7662 cx,
7663 )
7664 .pop()
7665 .unwrap();
7666
7667 let font_id =
7668 window.text_system().resolve_font(&style.text.font());
7669 let font_size =
7670 style.text.font_size.to_pixels(window.rem_size());
7671 let em_width =
7672 window.text_system().em_width(font_id, font_size).unwrap();
7673
7674 size(line.width + em_width, height)
7675 },
7676 )
7677 } else {
7678 let mut style = Style::default();
7679 style.size.height = height.into();
7680 style.size.width = relative(1.).into();
7681 window.request_layout(style, None, cx)
7682 }
7683 }
7684 EditorMode::AutoHeight { max_lines } => {
7685 let editor_handle = cx.entity().clone();
7686 let max_line_number_width =
7687 self.max_line_number_width(&editor.snapshot(window, cx), window, cx);
7688 window.request_measured_layout(
7689 Style::default(),
7690 move |known_dimensions, available_space, window, cx| {
7691 editor_handle
7692 .update(cx, |editor, cx| {
7693 compute_auto_height_layout(
7694 editor,
7695 max_lines,
7696 max_line_number_width,
7697 known_dimensions,
7698 available_space.width,
7699 window,
7700 cx,
7701 )
7702 })
7703 .unwrap_or_default()
7704 },
7705 )
7706 }
7707 EditorMode::Minimap { .. } => {
7708 let mut style = Style::default();
7709 style.size.width = relative(1.).into();
7710 style.size.height = relative(1.).into();
7711 window.request_layout(style, None, cx)
7712 }
7713 EditorMode::Full {
7714 sized_by_content, ..
7715 } => {
7716 let mut style = Style::default();
7717 style.size.width = relative(1.).into();
7718 if sized_by_content {
7719 let snapshot = editor.snapshot(window, cx);
7720 let line_height =
7721 self.style.text.line_height_in_pixels(window.rem_size());
7722 let scroll_height =
7723 (snapshot.max_point().row().next_row().0 as f32) * line_height;
7724 style.size.height = scroll_height.into();
7725 } else {
7726 style.size.height = relative(1.).into();
7727 }
7728 window.request_layout(style, None, cx)
7729 }
7730 };
7731
7732 (layout_id, ())
7733 })
7734 })
7735 }
7736
7737 fn prepaint(
7738 &mut self,
7739 _: Option<&GlobalElementId>,
7740 _inspector_id: Option<&gpui::InspectorElementId>,
7741 bounds: Bounds<Pixels>,
7742 _: &mut Self::RequestLayoutState,
7743 window: &mut Window,
7744 cx: &mut App,
7745 ) -> Self::PrepaintState {
7746 let text_style = TextStyleRefinement {
7747 font_size: Some(self.style.text.font_size),
7748 line_height: Some(self.style.text.line_height),
7749 ..Default::default()
7750 };
7751 let focus_handle = self.editor.focus_handle(cx);
7752 window.set_view_id(self.editor.entity_id());
7753 window.set_focus_handle(&focus_handle, cx);
7754
7755 let rem_size = self.rem_size(cx);
7756 window.with_rem_size(rem_size, |window| {
7757 window.with_text_style(Some(text_style), |window| {
7758 window.with_content_mask(Some(ContentMask { bounds }), |window| {
7759 let (mut snapshot, is_read_only) = self.editor.update(cx, |editor, cx| {
7760 (editor.snapshot(window, cx), editor.read_only(cx))
7761 });
7762 let style = self.style.clone();
7763
7764 let font_id = window.text_system().resolve_font(&style.text.font());
7765 let font_size = style.text.font_size.to_pixels(window.rem_size());
7766 let line_height = style.text.line_height_in_pixels(window.rem_size());
7767 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
7768 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
7769 let glyph_grid_cell = size(em_advance, line_height);
7770
7771 let gutter_dimensions = snapshot
7772 .gutter_dimensions(
7773 font_id,
7774 font_size,
7775 self.max_line_number_width(&snapshot, window, cx),
7776 cx,
7777 )
7778 .or_else(|| {
7779 self.editor.read(cx).offset_content.then(|| {
7780 GutterDimensions::default_with_margin(font_id, font_size, cx)
7781 })
7782 })
7783 .unwrap_or_default();
7784 let text_width = bounds.size.width - gutter_dimensions.width;
7785
7786 let settings = EditorSettings::get_global(cx);
7787 let scrollbars_shown = settings.scrollbar.show != ShowScrollbar::Never;
7788 let vertical_scrollbar_width = (scrollbars_shown
7789 && settings.scrollbar.axes.vertical
7790 && self.editor.read(cx).show_scrollbars.vertical)
7791 .then_some(style.scrollbar_width)
7792 .unwrap_or_default();
7793 let minimap_width = self
7794 .editor
7795 .read(cx)
7796 .minimap()
7797 .is_some()
7798 .then(|| match settings.minimap.show {
7799 ShowMinimap::Auto => {
7800 scrollbars_shown.then_some(MinimapLayout::MINIMAP_WIDTH)
7801 }
7802 _ => Some(MinimapLayout::MINIMAP_WIDTH),
7803 })
7804 .flatten()
7805 .filter(|minimap_width| {
7806 text_width - vertical_scrollbar_width - *minimap_width > *minimap_width
7807 })
7808 .unwrap_or_default();
7809
7810 let right_margin = minimap_width + vertical_scrollbar_width;
7811
7812 let editor_width =
7813 text_width - gutter_dimensions.margin - 2 * em_width - right_margin;
7814
7815 let editor_margins = EditorMargins {
7816 gutter: gutter_dimensions,
7817 right: right_margin,
7818 };
7819
7820 // Offset the content_bounds from the text_bounds by the gutter margin (which
7821 // is roughly half a character wide) to make hit testing work more like how we want.
7822 let content_offset = point(editor_margins.gutter.margin, Pixels::ZERO);
7823
7824 let editor_content_width = editor_width - content_offset.x;
7825
7826 snapshot = self.editor.update(cx, |editor, cx| {
7827 editor.last_bounds = Some(bounds);
7828 editor.gutter_dimensions = gutter_dimensions;
7829 editor.set_visible_line_count(bounds.size.height / line_height, window, cx);
7830
7831 if matches!(
7832 editor.mode,
7833 EditorMode::AutoHeight { .. } | EditorMode::Minimap { .. }
7834 ) {
7835 snapshot
7836 } else {
7837 let wrap_width_for = |column: u32| (column as f32 * em_advance).ceil();
7838 let wrap_width = match editor.soft_wrap_mode(cx) {
7839 SoftWrap::GitDiff => None,
7840 SoftWrap::None => Some(wrap_width_for(MAX_LINE_LEN as u32 / 2)),
7841 SoftWrap::EditorWidth => Some(editor_content_width),
7842 SoftWrap::Column(column) => Some(wrap_width_for(column)),
7843 SoftWrap::Bounded(column) => {
7844 Some(editor_content_width.min(wrap_width_for(column)))
7845 }
7846 };
7847
7848 if editor.set_wrap_width(wrap_width, cx) {
7849 editor.snapshot(window, cx)
7850 } else {
7851 snapshot
7852 }
7853 }
7854 });
7855
7856 let wrap_guides = self
7857 .editor
7858 .read(cx)
7859 .wrap_guides(cx)
7860 .iter()
7861 .map(|(guide, active)| (self.column_pixels(*guide, window, cx), *active))
7862 .collect::<SmallVec<[_; 2]>>();
7863
7864 let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
7865 let gutter_hitbox = window.insert_hitbox(
7866 gutter_bounds(bounds, gutter_dimensions),
7867 HitboxBehavior::Normal,
7868 );
7869 let text_hitbox = window.insert_hitbox(
7870 Bounds {
7871 origin: gutter_hitbox.top_right(),
7872 size: size(text_width, bounds.size.height),
7873 },
7874 HitboxBehavior::Normal,
7875 );
7876
7877 let content_origin = text_hitbox.origin + content_offset;
7878
7879 let editor_text_bounds =
7880 Bounds::from_corners(content_origin, bounds.bottom_right());
7881
7882 let height_in_lines = editor_text_bounds.size.height / line_height;
7883
7884 let max_row = snapshot.max_point().row().as_f32();
7885
7886 // The max scroll position for the top of the window
7887 let max_scroll_top = if matches!(
7888 snapshot.mode,
7889 EditorMode::SingleLine { .. }
7890 | EditorMode::AutoHeight { .. }
7891 | EditorMode::Full {
7892 sized_by_content: true,
7893 ..
7894 }
7895 ) {
7896 (max_row - height_in_lines + 1.).max(0.)
7897 } else {
7898 let settings = EditorSettings::get_global(cx);
7899 match settings.scroll_beyond_last_line {
7900 ScrollBeyondLastLine::OnePage => max_row,
7901 ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.).max(0.),
7902 ScrollBeyondLastLine::VerticalScrollMargin => {
7903 (max_row - height_in_lines + 1. + settings.vertical_scroll_margin)
7904 .max(0.)
7905 }
7906 }
7907 };
7908
7909 // TODO: Autoscrolling for both axes
7910 let mut autoscroll_request = None;
7911 let mut autoscroll_containing_element = false;
7912 let mut autoscroll_horizontally = false;
7913 self.editor.update(cx, |editor, cx| {
7914 autoscroll_request = editor.autoscroll_request();
7915 autoscroll_containing_element =
7916 autoscroll_request.is_some() || editor.has_pending_selection();
7917 // TODO: Is this horizontal or vertical?!
7918 autoscroll_horizontally = editor.autoscroll_vertically(
7919 bounds,
7920 line_height,
7921 max_scroll_top,
7922 window,
7923 cx,
7924 );
7925 snapshot = editor.snapshot(window, cx);
7926 });
7927
7928 let mut scroll_position = snapshot.scroll_position();
7929 // The scroll position is a fractional point, the whole number of which represents
7930 // the top of the window in terms of display rows.
7931 let start_row = DisplayRow(scroll_position.y as u32);
7932 let max_row = snapshot.max_point().row();
7933 let end_row = cmp::min(
7934 (scroll_position.y + height_in_lines).ceil() as u32,
7935 max_row.next_row().0,
7936 );
7937 let end_row = DisplayRow(end_row);
7938
7939 let row_infos = snapshot
7940 .row_infos(start_row)
7941 .take((start_row..end_row).len())
7942 .collect::<Vec<RowInfo>>();
7943 let is_row_soft_wrapped = |row: usize| {
7944 row_infos
7945 .get(row)
7946 .map_or(true, |info| info.buffer_row.is_none())
7947 };
7948
7949 let start_anchor = if start_row == Default::default() {
7950 Anchor::min()
7951 } else {
7952 snapshot.buffer_snapshot.anchor_before(
7953 DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left),
7954 )
7955 };
7956 let end_anchor = if end_row > max_row {
7957 Anchor::max()
7958 } else {
7959 snapshot.buffer_snapshot.anchor_before(
7960 DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right),
7961 )
7962 };
7963
7964 let mut highlighted_rows = self
7965 .editor
7966 .update(cx, |editor, cx| editor.highlighted_display_rows(window, cx));
7967
7968 let is_light = cx.theme().appearance().is_light();
7969
7970 for (ix, row_info) in row_infos.iter().enumerate() {
7971 let Some(diff_status) = row_info.diff_status else {
7972 continue;
7973 };
7974
7975 let background_color = match diff_status.kind {
7976 DiffHunkStatusKind::Added => cx.theme().colors().version_control_added,
7977 DiffHunkStatusKind::Deleted => {
7978 cx.theme().colors().version_control_deleted
7979 }
7980 DiffHunkStatusKind::Modified => {
7981 debug_panic!("modified diff status for row info");
7982 continue;
7983 }
7984 };
7985
7986 let hunk_opacity = if is_light { 0.16 } else { 0.12 };
7987
7988 let hollow_highlight = LineHighlight {
7989 background: (background_color.opacity(if is_light {
7990 0.08
7991 } else {
7992 0.06
7993 }))
7994 .into(),
7995 border: Some(if is_light {
7996 background_color.opacity(0.48)
7997 } else {
7998 background_color.opacity(0.36)
7999 }),
8000 include_gutter: true,
8001 type_id: None,
8002 };
8003
8004 let filled_highlight = LineHighlight {
8005 background: solid_background(background_color.opacity(hunk_opacity)),
8006 border: None,
8007 include_gutter: true,
8008 type_id: None,
8009 };
8010
8011 let background = if Self::diff_hunk_hollow(diff_status, cx) {
8012 hollow_highlight
8013 } else {
8014 filled_highlight
8015 };
8016
8017 highlighted_rows
8018 .entry(start_row + DisplayRow(ix as u32))
8019 .or_insert(background);
8020 }
8021
8022 let highlighted_ranges = self
8023 .editor_with_selections(cx)
8024 .map(|editor| {
8025 editor.read(cx).background_highlights_in_range(
8026 start_anchor..end_anchor,
8027 &snapshot.display_snapshot,
8028 cx.theme().colors(),
8029 )
8030 })
8031 .unwrap_or_default();
8032 let highlighted_gutter_ranges =
8033 self.editor.read(cx).gutter_highlights_in_range(
8034 start_anchor..end_anchor,
8035 &snapshot.display_snapshot,
8036 cx,
8037 );
8038
8039 let redacted_ranges = self.editor.read(cx).redacted_ranges(
8040 start_anchor..end_anchor,
8041 &snapshot.display_snapshot,
8042 cx,
8043 );
8044
8045 let (local_selections, selected_buffer_ids): (
8046 Vec<Selection<Point>>,
8047 Vec<BufferId>,
8048 ) = self
8049 .editor_with_selections(cx)
8050 .map(|editor| {
8051 editor.update(cx, |editor, cx| {
8052 let all_selections = editor.selections.all::<Point>(cx);
8053 let selected_buffer_ids = if editor.is_singleton(cx) {
8054 Vec::new()
8055 } else {
8056 let mut selected_buffer_ids =
8057 Vec::with_capacity(all_selections.len());
8058
8059 for selection in all_selections {
8060 for buffer_id in snapshot
8061 .buffer_snapshot
8062 .buffer_ids_for_range(selection.range())
8063 {
8064 if selected_buffer_ids.last() != Some(&buffer_id) {
8065 selected_buffer_ids.push(buffer_id);
8066 }
8067 }
8068 }
8069
8070 selected_buffer_ids
8071 };
8072
8073 let mut selections = editor
8074 .selections
8075 .disjoint_in_range(start_anchor..end_anchor, cx);
8076 selections.extend(editor.selections.pending(cx));
8077
8078 (selections, selected_buffer_ids)
8079 })
8080 })
8081 .unwrap_or_default();
8082
8083 let (selections, mut active_rows, newest_selection_head) = self
8084 .layout_selections(
8085 start_anchor,
8086 end_anchor,
8087 &local_selections,
8088 &snapshot,
8089 start_row,
8090 end_row,
8091 window,
8092 cx,
8093 );
8094 let mut breakpoint_rows = self.editor.update(cx, |editor, cx| {
8095 editor.active_breakpoints(start_row..end_row, window, cx)
8096 });
8097 if cx.has_flag::<DebuggerFeatureFlag>() {
8098 for (display_row, (_, bp, state)) in &breakpoint_rows {
8099 if bp.is_enabled() && state.is_none_or(|s| s.verified) {
8100 active_rows.entry(*display_row).or_default().breakpoint = true;
8101 }
8102 }
8103 }
8104
8105 let line_numbers = self.layout_line_numbers(
8106 Some(&gutter_hitbox),
8107 gutter_dimensions,
8108 line_height,
8109 scroll_position,
8110 start_row..end_row,
8111 &row_infos,
8112 &active_rows,
8113 newest_selection_head,
8114 &snapshot,
8115 window,
8116 cx,
8117 );
8118
8119 // We add the gutter breakpoint indicator to breakpoint_rows after painting
8120 // line numbers so we don't paint a line number debug accent color if a user
8121 // has their mouse over that line when a breakpoint isn't there
8122 if cx.has_flag::<DebuggerFeatureFlag>() {
8123 self.editor.update(cx, |editor, _| {
8124 if let Some(phantom_breakpoint) = &mut editor
8125 .gutter_breakpoint_indicator
8126 .0
8127 .filter(|phantom_breakpoint| phantom_breakpoint.is_active)
8128 {
8129 // Is there a non-phantom breakpoint on this line?
8130 phantom_breakpoint.collides_with_existing_breakpoint = true;
8131 breakpoint_rows
8132 .entry(phantom_breakpoint.display_row)
8133 .or_insert_with(|| {
8134 let position = snapshot.display_point_to_anchor(
8135 DisplayPoint::new(phantom_breakpoint.display_row, 0),
8136 Bias::Right,
8137 );
8138 let breakpoint = Breakpoint::new_standard();
8139 phantom_breakpoint.collides_with_existing_breakpoint =
8140 false;
8141 (position, breakpoint, None)
8142 });
8143 }
8144 })
8145 }
8146
8147 let mut expand_toggles =
8148 window.with_element_namespace("expand_toggles", |window| {
8149 self.layout_expand_toggles(
8150 &gutter_hitbox,
8151 gutter_dimensions,
8152 em_width,
8153 line_height,
8154 scroll_position,
8155 &row_infos,
8156 window,
8157 cx,
8158 )
8159 });
8160
8161 let mut crease_toggles =
8162 window.with_element_namespace("crease_toggles", |window| {
8163 self.layout_crease_toggles(
8164 start_row..end_row,
8165 &row_infos,
8166 &active_rows,
8167 &snapshot,
8168 window,
8169 cx,
8170 )
8171 });
8172 let crease_trailers =
8173 window.with_element_namespace("crease_trailers", |window| {
8174 self.layout_crease_trailers(
8175 row_infos.iter().copied(),
8176 &snapshot,
8177 window,
8178 cx,
8179 )
8180 });
8181
8182 let display_hunks = self.layout_gutter_diff_hunks(
8183 line_height,
8184 &gutter_hitbox,
8185 start_row..end_row,
8186 &snapshot,
8187 window,
8188 cx,
8189 );
8190
8191 let mut line_layouts = Self::layout_lines(
8192 start_row..end_row,
8193 &snapshot,
8194 &self.style,
8195 editor_width,
8196 is_row_soft_wrapped,
8197 window,
8198 cx,
8199 );
8200 let new_fold_widths = line_layouts
8201 .iter()
8202 .flat_map(|layout| &layout.fragments)
8203 .filter_map(|fragment| {
8204 if let LineFragment::Element { id, size, .. } = fragment {
8205 Some((*id, size.width))
8206 } else {
8207 None
8208 }
8209 });
8210 if self.editor.update(cx, |editor, cx| {
8211 editor.update_fold_widths(new_fold_widths, cx)
8212 }) {
8213 // If the fold widths have changed, we need to prepaint
8214 // the element again to account for any changes in
8215 // wrapping.
8216 return self.prepaint(None, _inspector_id, bounds, &mut (), window, cx);
8217 }
8218
8219 let longest_line_blame_width = self
8220 .editor
8221 .update(cx, |editor, cx| {
8222 if !editor.show_git_blame_inline {
8223 return None;
8224 }
8225 let blame = editor.blame.as_ref()?;
8226 let blame_entry = blame
8227 .update(cx, |blame, cx| {
8228 let row_infos =
8229 snapshot.row_infos(snapshot.longest_row()).next()?;
8230 blame.blame_for_rows(&[row_infos], cx).next()
8231 })
8232 .flatten()?;
8233 let mut element = render_inline_blame_entry(blame_entry, &style, cx)?;
8234 let inline_blame_padding = INLINE_BLAME_PADDING_EM_WIDTHS * em_advance;
8235 Some(
8236 element
8237 .layout_as_root(AvailableSpace::min_size(), window, cx)
8238 .width
8239 + inline_blame_padding,
8240 )
8241 })
8242 .unwrap_or(Pixels::ZERO);
8243
8244 let longest_line_width = layout_line(
8245 snapshot.longest_row(),
8246 &snapshot,
8247 &style,
8248 editor_width,
8249 is_row_soft_wrapped,
8250 window,
8251 cx,
8252 )
8253 .width;
8254
8255 let scrollbar_layout_information = ScrollbarLayoutInformation::new(
8256 text_hitbox.bounds,
8257 glyph_grid_cell,
8258 size(longest_line_width, max_row.as_f32() * line_height),
8259 longest_line_blame_width,
8260 editor_width,
8261 EditorSettings::get_global(cx),
8262 );
8263
8264 let mut scroll_width = scrollbar_layout_information.scroll_range.width;
8265
8266 let sticky_header_excerpt = if snapshot.buffer_snapshot.show_headers() {
8267 snapshot.sticky_header_excerpt(scroll_position.y)
8268 } else {
8269 None
8270 };
8271 let sticky_header_excerpt_id =
8272 sticky_header_excerpt.as_ref().map(|top| top.excerpt.id);
8273
8274 let blocks = window.with_element_namespace("blocks", |window| {
8275 self.render_blocks(
8276 start_row..end_row,
8277 &snapshot,
8278 &hitbox,
8279 &text_hitbox,
8280 editor_width,
8281 &mut scroll_width,
8282 &editor_margins,
8283 em_width,
8284 gutter_dimensions.full_width(),
8285 line_height,
8286 &mut line_layouts,
8287 &local_selections,
8288 &selected_buffer_ids,
8289 is_row_soft_wrapped,
8290 sticky_header_excerpt_id,
8291 window,
8292 cx,
8293 )
8294 });
8295 let (mut blocks, row_block_types) = match blocks {
8296 Ok(blocks) => blocks,
8297 Err(resized_blocks) => {
8298 self.editor.update(cx, |editor, cx| {
8299 editor.resize_blocks(resized_blocks, autoscroll_request, cx)
8300 });
8301 return self.prepaint(None, _inspector_id, bounds, &mut (), window, cx);
8302 }
8303 };
8304
8305 let sticky_buffer_header = sticky_header_excerpt.map(|sticky_header_excerpt| {
8306 window.with_element_namespace("blocks", |window| {
8307 self.layout_sticky_buffer_header(
8308 sticky_header_excerpt,
8309 scroll_position.y,
8310 line_height,
8311 right_margin,
8312 &snapshot,
8313 &hitbox,
8314 &selected_buffer_ids,
8315 &blocks,
8316 window,
8317 cx,
8318 )
8319 })
8320 });
8321
8322 let start_buffer_row =
8323 MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
8324 let end_buffer_row =
8325 MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
8326
8327 let scroll_max = point(
8328 ((scroll_width - editor_content_width) / em_advance).max(0.0),
8329 max_scroll_top,
8330 );
8331
8332 self.editor.update(cx, |editor, cx| {
8333 let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
8334
8335 let autoscrolled = if autoscroll_horizontally {
8336 editor.autoscroll_horizontally(
8337 start_row,
8338 editor_content_width,
8339 scroll_width,
8340 em_advance,
8341 &line_layouts,
8342 cx,
8343 )
8344 } else {
8345 false
8346 };
8347
8348 if clamped || autoscrolled {
8349 snapshot = editor.snapshot(window, cx);
8350 scroll_position = snapshot.scroll_position();
8351 }
8352 });
8353
8354 let scroll_pixel_position = point(
8355 scroll_position.x * em_advance,
8356 scroll_position.y * line_height,
8357 );
8358 let indent_guides = self.layout_indent_guides(
8359 content_origin,
8360 text_hitbox.origin,
8361 start_buffer_row..end_buffer_row,
8362 scroll_pixel_position,
8363 line_height,
8364 &snapshot,
8365 window,
8366 cx,
8367 );
8368
8369 let crease_trailers =
8370 window.with_element_namespace("crease_trailers", |window| {
8371 self.prepaint_crease_trailers(
8372 crease_trailers,
8373 &line_layouts,
8374 line_height,
8375 content_origin,
8376 scroll_pixel_position,
8377 em_width,
8378 window,
8379 cx,
8380 )
8381 });
8382
8383 let (inline_completion_popover, inline_completion_popover_origin) = self
8384 .editor
8385 .update(cx, |editor, cx| {
8386 editor.render_edit_prediction_popover(
8387 &text_hitbox.bounds,
8388 content_origin,
8389 right_margin,
8390 &snapshot,
8391 start_row..end_row,
8392 scroll_position.y,
8393 scroll_position.y + height_in_lines,
8394 &line_layouts,
8395 line_height,
8396 scroll_pixel_position,
8397 newest_selection_head,
8398 editor_width,
8399 &style,
8400 window,
8401 cx,
8402 )
8403 })
8404 .unzip();
8405
8406 let mut inline_diagnostics = self.layout_inline_diagnostics(
8407 &line_layouts,
8408 &crease_trailers,
8409 &row_block_types,
8410 content_origin,
8411 scroll_pixel_position,
8412 inline_completion_popover_origin,
8413 start_row,
8414 end_row,
8415 line_height,
8416 em_width,
8417 &style,
8418 window,
8419 cx,
8420 );
8421
8422 let mut inline_blame_layout = None;
8423 let mut inline_code_actions = None;
8424 if let Some(newest_selection_head) = newest_selection_head {
8425 let display_row = newest_selection_head.row();
8426 if (start_row..end_row).contains(&display_row)
8427 && !row_block_types.contains_key(&display_row)
8428 {
8429 inline_code_actions = self.layout_inline_code_actions(
8430 newest_selection_head,
8431 content_origin,
8432 scroll_pixel_position,
8433 line_height,
8434 &snapshot,
8435 window,
8436 cx,
8437 );
8438
8439 let line_ix = display_row.minus(start_row) as usize;
8440 let row_info = &row_infos[line_ix];
8441 let line_layout = &line_layouts[line_ix];
8442 let crease_trailer_layout = crease_trailers[line_ix].as_ref();
8443
8444 if let Some(layout) = self.layout_inline_blame(
8445 display_row,
8446 row_info,
8447 line_layout,
8448 crease_trailer_layout,
8449 em_width,
8450 content_origin,
8451 scroll_pixel_position,
8452 line_height,
8453 &text_hitbox,
8454 window,
8455 cx,
8456 ) {
8457 inline_blame_layout = Some(layout);
8458 // Blame overrides inline diagnostics
8459 inline_diagnostics.remove(&display_row);
8460 }
8461 }
8462 }
8463
8464 let blamed_display_rows = self.layout_blame_entries(
8465 &row_infos,
8466 em_width,
8467 scroll_position,
8468 line_height,
8469 &gutter_hitbox,
8470 gutter_dimensions.git_blame_entries_width,
8471 window,
8472 cx,
8473 );
8474
8475 self.editor.update(cx, |editor, cx| {
8476 let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
8477
8478 let autoscrolled = if autoscroll_horizontally {
8479 editor.autoscroll_horizontally(
8480 start_row,
8481 editor_content_width,
8482 scroll_width,
8483 em_width,
8484 &line_layouts,
8485 cx,
8486 )
8487 } else {
8488 false
8489 };
8490
8491 if clamped || autoscrolled {
8492 snapshot = editor.snapshot(window, cx);
8493 scroll_position = snapshot.scroll_position();
8494 }
8495 });
8496
8497 let line_elements = self.prepaint_lines(
8498 start_row,
8499 &mut line_layouts,
8500 line_height,
8501 scroll_pixel_position,
8502 content_origin,
8503 window,
8504 cx,
8505 );
8506
8507 window.with_element_namespace("blocks", |window| {
8508 self.layout_blocks(
8509 &mut blocks,
8510 &hitbox,
8511 line_height,
8512 scroll_pixel_position,
8513 window,
8514 cx,
8515 );
8516 });
8517
8518 let cursors = self.collect_cursors(&snapshot, cx);
8519 let visible_row_range = start_row..end_row;
8520 let non_visible_cursors = cursors
8521 .iter()
8522 .any(|c| !visible_row_range.contains(&c.0.row()));
8523
8524 let visible_cursors = self.layout_visible_cursors(
8525 &snapshot,
8526 &selections,
8527 &row_block_types,
8528 start_row..end_row,
8529 &line_layouts,
8530 &text_hitbox,
8531 content_origin,
8532 scroll_position,
8533 scroll_pixel_position,
8534 line_height,
8535 em_width,
8536 em_advance,
8537 autoscroll_containing_element,
8538 window,
8539 cx,
8540 );
8541
8542 let scrollbars_layout = self.layout_scrollbars(
8543 &snapshot,
8544 &scrollbar_layout_information,
8545 content_offset,
8546 scroll_position,
8547 non_visible_cursors,
8548 right_margin,
8549 editor_width,
8550 window,
8551 cx,
8552 );
8553
8554 let gutter_settings = EditorSettings::get_global(cx).gutter;
8555
8556 let context_menu_layout =
8557 if let Some(newest_selection_head) = newest_selection_head {
8558 let newest_selection_point =
8559 newest_selection_head.to_point(&snapshot.display_snapshot);
8560 if (start_row..end_row).contains(&newest_selection_head.row()) {
8561 self.layout_cursor_popovers(
8562 line_height,
8563 &text_hitbox,
8564 content_origin,
8565 right_margin,
8566 start_row,
8567 scroll_pixel_position,
8568 &line_layouts,
8569 newest_selection_head,
8570 newest_selection_point,
8571 &style,
8572 window,
8573 cx,
8574 )
8575 } else {
8576 None
8577 }
8578 } else {
8579 None
8580 };
8581
8582 self.layout_gutter_menu(
8583 line_height,
8584 &text_hitbox,
8585 content_origin,
8586 right_margin,
8587 scroll_pixel_position,
8588 gutter_dimensions.width - gutter_dimensions.left_padding,
8589 window,
8590 cx,
8591 );
8592
8593 let test_indicators = if gutter_settings.runnables {
8594 self.layout_run_indicators(
8595 line_height,
8596 start_row..end_row,
8597 &row_infos,
8598 scroll_pixel_position,
8599 &gutter_dimensions,
8600 &gutter_hitbox,
8601 &display_hunks,
8602 &snapshot,
8603 &mut breakpoint_rows,
8604 window,
8605 cx,
8606 )
8607 } else {
8608 Vec::new()
8609 };
8610
8611 let show_breakpoints = snapshot
8612 .show_breakpoints
8613 .unwrap_or(gutter_settings.breakpoints);
8614 let breakpoints = if cx.has_flag::<DebuggerFeatureFlag>() && show_breakpoints {
8615 self.layout_breakpoints(
8616 line_height,
8617 start_row..end_row,
8618 scroll_pixel_position,
8619 &gutter_dimensions,
8620 &gutter_hitbox,
8621 &display_hunks,
8622 &snapshot,
8623 breakpoint_rows,
8624 &row_infos,
8625 window,
8626 cx,
8627 )
8628 } else {
8629 vec![]
8630 };
8631
8632 self.layout_signature_help(
8633 &hitbox,
8634 content_origin,
8635 scroll_pixel_position,
8636 newest_selection_head,
8637 start_row,
8638 &line_layouts,
8639 line_height,
8640 em_width,
8641 context_menu_layout,
8642 window,
8643 cx,
8644 );
8645
8646 if !cx.has_active_drag() {
8647 self.layout_hover_popovers(
8648 &snapshot,
8649 &hitbox,
8650 start_row..end_row,
8651 content_origin,
8652 scroll_pixel_position,
8653 &line_layouts,
8654 line_height,
8655 em_width,
8656 context_menu_layout,
8657 window,
8658 cx,
8659 );
8660 }
8661
8662 let mouse_context_menu = self.layout_mouse_context_menu(
8663 &snapshot,
8664 start_row..end_row,
8665 content_origin,
8666 window,
8667 cx,
8668 );
8669
8670 window.with_element_namespace("crease_toggles", |window| {
8671 self.prepaint_crease_toggles(
8672 &mut crease_toggles,
8673 line_height,
8674 &gutter_dimensions,
8675 gutter_settings,
8676 scroll_pixel_position,
8677 &gutter_hitbox,
8678 window,
8679 cx,
8680 )
8681 });
8682
8683 window.with_element_namespace("expand_toggles", |window| {
8684 self.prepaint_expand_toggles(&mut expand_toggles, window, cx)
8685 });
8686
8687 let minimap = window.with_element_namespace("minimap", |window| {
8688 self.layout_minimap(
8689 &snapshot,
8690 minimap_width,
8691 scroll_position,
8692 &scrollbar_layout_information,
8693 scrollbars_layout.as_ref(),
8694 window,
8695 cx,
8696 )
8697 });
8698
8699 let invisible_symbol_font_size = font_size / 2.;
8700 let tab_invisible = window.text_system().shape_line(
8701 "→".into(),
8702 invisible_symbol_font_size,
8703 &[TextRun {
8704 len: "→".len(),
8705 font: self.style.text.font(),
8706 color: cx.theme().colors().editor_invisible,
8707 background_color: None,
8708 underline: None,
8709 strikethrough: None,
8710 }],
8711 );
8712 let space_invisible = window.text_system().shape_line(
8713 "•".into(),
8714 invisible_symbol_font_size,
8715 &[TextRun {
8716 len: "•".len(),
8717 font: self.style.text.font(),
8718 color: cx.theme().colors().editor_invisible,
8719 background_color: None,
8720 underline: None,
8721 strikethrough: None,
8722 }],
8723 );
8724
8725 let mode = snapshot.mode.clone();
8726
8727 let (diff_hunk_controls, diff_hunk_control_bounds) = if is_read_only {
8728 (vec![], vec![])
8729 } else {
8730 self.layout_diff_hunk_controls(
8731 start_row..end_row,
8732 &row_infos,
8733 &text_hitbox,
8734 newest_selection_head,
8735 line_height,
8736 right_margin,
8737 scroll_pixel_position,
8738 &display_hunks,
8739 &highlighted_rows,
8740 self.editor.clone(),
8741 window,
8742 cx,
8743 )
8744 };
8745
8746 let position_map = Rc::new(PositionMap {
8747 size: bounds.size,
8748 visible_row_range,
8749 scroll_pixel_position,
8750 scroll_max,
8751 line_layouts,
8752 line_height,
8753 em_width,
8754 em_advance,
8755 snapshot,
8756 gutter_hitbox: gutter_hitbox.clone(),
8757 text_hitbox: text_hitbox.clone(),
8758 inline_blame_bounds: inline_blame_layout
8759 .as_ref()
8760 .map(|layout| (layout.bounds, layout.entry.clone())),
8761 display_hunks: display_hunks.clone(),
8762 diff_hunk_control_bounds: diff_hunk_control_bounds.clone(),
8763 });
8764
8765 self.editor.update(cx, |editor, _| {
8766 editor.last_position_map = Some(position_map.clone())
8767 });
8768
8769 EditorLayout {
8770 mode,
8771 position_map,
8772 visible_display_row_range: start_row..end_row,
8773 wrap_guides,
8774 indent_guides,
8775 hitbox,
8776 gutter_hitbox,
8777 display_hunks,
8778 content_origin,
8779 scrollbars_layout,
8780 minimap,
8781 active_rows,
8782 highlighted_rows,
8783 highlighted_ranges,
8784 highlighted_gutter_ranges,
8785 redacted_ranges,
8786 line_elements,
8787 line_numbers,
8788 blamed_display_rows,
8789 inline_diagnostics,
8790 inline_blame_layout,
8791 inline_code_actions,
8792 blocks,
8793 cursors,
8794 visible_cursors,
8795 selections,
8796 inline_completion_popover,
8797 diff_hunk_controls,
8798 mouse_context_menu,
8799 test_indicators,
8800 breakpoints,
8801 crease_toggles,
8802 crease_trailers,
8803 tab_invisible,
8804 space_invisible,
8805 sticky_buffer_header,
8806 expand_toggles,
8807 }
8808 })
8809 })
8810 })
8811 }
8812
8813 fn paint(
8814 &mut self,
8815 _: Option<&GlobalElementId>,
8816 __inspector_id: Option<&gpui::InspectorElementId>,
8817 bounds: Bounds<gpui::Pixels>,
8818 _: &mut Self::RequestLayoutState,
8819 layout: &mut Self::PrepaintState,
8820 window: &mut Window,
8821 cx: &mut App,
8822 ) {
8823 let focus_handle = self.editor.focus_handle(cx);
8824 let key_context = self
8825 .editor
8826 .update(cx, |editor, cx| editor.key_context(window, cx));
8827
8828 window.set_key_context(key_context);
8829 window.handle_input(
8830 &focus_handle,
8831 ElementInputHandler::new(bounds, self.editor.clone()),
8832 cx,
8833 );
8834 self.register_actions(window, cx);
8835 self.register_key_listeners(window, cx, layout);
8836
8837 let text_style = TextStyleRefinement {
8838 font_size: Some(self.style.text.font_size),
8839 line_height: Some(self.style.text.line_height),
8840 ..Default::default()
8841 };
8842 let rem_size = self.rem_size(cx);
8843 window.with_rem_size(rem_size, |window| {
8844 window.with_text_style(Some(text_style), |window| {
8845 window.with_content_mask(Some(ContentMask { bounds }), |window| {
8846 self.paint_mouse_listeners(layout, window, cx);
8847 self.paint_background(layout, window, cx);
8848 self.paint_indent_guides(layout, window, cx);
8849
8850 if layout.gutter_hitbox.size.width > Pixels::ZERO {
8851 self.paint_blamed_display_rows(layout, window, cx);
8852 self.paint_line_numbers(layout, window, cx);
8853 }
8854
8855 self.paint_text(layout, window, cx);
8856
8857 if layout.gutter_hitbox.size.width > Pixels::ZERO {
8858 self.paint_gutter_highlights(layout, window, cx);
8859 self.paint_gutter_indicators(layout, window, cx);
8860 }
8861
8862 if !layout.blocks.is_empty() {
8863 window.with_element_namespace("blocks", |window| {
8864 self.paint_blocks(layout, window, cx);
8865 });
8866 }
8867
8868 window.with_element_namespace("blocks", |window| {
8869 if let Some(mut sticky_header) = layout.sticky_buffer_header.take() {
8870 sticky_header.paint(window, cx)
8871 }
8872 });
8873
8874 self.paint_minimap(layout, window, cx);
8875 self.paint_scrollbars(layout, window, cx);
8876 self.paint_inline_completion_popover(layout, window, cx);
8877 self.paint_mouse_context_menu(layout, window, cx);
8878 });
8879 })
8880 })
8881 }
8882}
8883
8884pub(super) fn gutter_bounds(
8885 editor_bounds: Bounds<Pixels>,
8886 gutter_dimensions: GutterDimensions,
8887) -> Bounds<Pixels> {
8888 Bounds {
8889 origin: editor_bounds.origin,
8890 size: size(gutter_dimensions.width, editor_bounds.size.height),
8891 }
8892}
8893
8894#[derive(Clone, Copy)]
8895struct ContextMenuLayout {
8896 y_flipped: bool,
8897 bounds: Bounds<Pixels>,
8898}
8899
8900/// Holds information required for layouting the editor scrollbars.
8901struct ScrollbarLayoutInformation {
8902 /// The bounds of the editor area (excluding the content offset).
8903 editor_bounds: Bounds<Pixels>,
8904 /// The available range to scroll within the document.
8905 scroll_range: Size<Pixels>,
8906 /// The space available for one glyph in the editor.
8907 glyph_grid_cell: Size<Pixels>,
8908}
8909
8910impl ScrollbarLayoutInformation {
8911 pub fn new(
8912 editor_bounds: Bounds<Pixels>,
8913 glyph_grid_cell: Size<Pixels>,
8914 document_size: Size<Pixels>,
8915 longest_line_blame_width: Pixels,
8916 editor_width: Pixels,
8917 settings: &EditorSettings,
8918 ) -> Self {
8919 let vertical_overscroll = match settings.scroll_beyond_last_line {
8920 ScrollBeyondLastLine::OnePage => editor_bounds.size.height,
8921 ScrollBeyondLastLine::Off => glyph_grid_cell.height,
8922 ScrollBeyondLastLine::VerticalScrollMargin => {
8923 (1.0 + settings.vertical_scroll_margin) * glyph_grid_cell.height
8924 }
8925 };
8926
8927 let right_margin = if document_size.width + longest_line_blame_width >= editor_width {
8928 glyph_grid_cell.width
8929 } else {
8930 px(0.0)
8931 };
8932
8933 let overscroll = size(right_margin + longest_line_blame_width, vertical_overscroll);
8934
8935 let scroll_range = document_size + overscroll;
8936
8937 ScrollbarLayoutInformation {
8938 editor_bounds,
8939 scroll_range,
8940 glyph_grid_cell,
8941 }
8942 }
8943}
8944
8945impl IntoElement for EditorElement {
8946 type Element = Self;
8947
8948 fn into_element(self) -> Self::Element {
8949 self
8950 }
8951}
8952
8953pub struct EditorLayout {
8954 position_map: Rc<PositionMap>,
8955 hitbox: Hitbox,
8956 gutter_hitbox: Hitbox,
8957 content_origin: gpui::Point<Pixels>,
8958 scrollbars_layout: Option<EditorScrollbars>,
8959 minimap: Option<MinimapLayout>,
8960 mode: EditorMode,
8961 wrap_guides: SmallVec<[(Pixels, bool); 2]>,
8962 indent_guides: Option<Vec<IndentGuideLayout>>,
8963 visible_display_row_range: Range<DisplayRow>,
8964 active_rows: BTreeMap<DisplayRow, LineHighlightSpec>,
8965 highlighted_rows: BTreeMap<DisplayRow, LineHighlight>,
8966 line_elements: SmallVec<[AnyElement; 1]>,
8967 line_numbers: Arc<HashMap<MultiBufferRow, LineNumberLayout>>,
8968 display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
8969 blamed_display_rows: Option<Vec<AnyElement>>,
8970 inline_diagnostics: HashMap<DisplayRow, AnyElement>,
8971 inline_blame_layout: Option<InlineBlameLayout>,
8972 inline_code_actions: Option<AnyElement>,
8973 blocks: Vec<BlockLayout>,
8974 highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
8975 highlighted_gutter_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
8976 redacted_ranges: Vec<Range<DisplayPoint>>,
8977 cursors: Vec<(DisplayPoint, Hsla)>,
8978 visible_cursors: Vec<CursorLayout>,
8979 selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
8980 test_indicators: Vec<AnyElement>,
8981 breakpoints: Vec<AnyElement>,
8982 crease_toggles: Vec<Option<AnyElement>>,
8983 expand_toggles: Vec<Option<(AnyElement, gpui::Point<Pixels>)>>,
8984 diff_hunk_controls: Vec<AnyElement>,
8985 crease_trailers: Vec<Option<CreaseTrailerLayout>>,
8986 inline_completion_popover: Option<AnyElement>,
8987 mouse_context_menu: Option<AnyElement>,
8988 tab_invisible: ShapedLine,
8989 space_invisible: ShapedLine,
8990 sticky_buffer_header: Option<AnyElement>,
8991}
8992
8993impl EditorLayout {
8994 fn line_end_overshoot(&self) -> Pixels {
8995 0.15 * self.position_map.line_height
8996 }
8997}
8998
8999struct LineNumberLayout {
9000 shaped_line: ShapedLine,
9001 hitbox: Option<Hitbox>,
9002}
9003
9004struct ColoredRange<T> {
9005 start: T,
9006 end: T,
9007 color: Hsla,
9008}
9009
9010impl Along for ScrollbarAxes {
9011 type Unit = bool;
9012
9013 fn along(&self, axis: ScrollbarAxis) -> Self::Unit {
9014 match axis {
9015 ScrollbarAxis::Horizontal => self.horizontal,
9016 ScrollbarAxis::Vertical => self.vertical,
9017 }
9018 }
9019
9020 fn apply_along(&self, axis: ScrollbarAxis, f: impl FnOnce(Self::Unit) -> Self::Unit) -> Self {
9021 match axis {
9022 ScrollbarAxis::Horizontal => ScrollbarAxes {
9023 horizontal: f(self.horizontal),
9024 vertical: self.vertical,
9025 },
9026 ScrollbarAxis::Vertical => ScrollbarAxes {
9027 horizontal: self.horizontal,
9028 vertical: f(self.vertical),
9029 },
9030 }
9031 }
9032}
9033
9034#[derive(Clone)]
9035struct EditorScrollbars {
9036 pub vertical: Option<ScrollbarLayout>,
9037 pub horizontal: Option<ScrollbarLayout>,
9038 pub visible: bool,
9039}
9040
9041impl EditorScrollbars {
9042 pub fn from_scrollbar_axes(
9043 settings_visibility: ScrollbarAxes,
9044 layout_information: &ScrollbarLayoutInformation,
9045 content_offset: gpui::Point<Pixels>,
9046 scroll_position: gpui::Point<f32>,
9047 scrollbar_width: Pixels,
9048 right_margin: Pixels,
9049 editor_width: Pixels,
9050 show_scrollbars: bool,
9051 scrollbar_state: Option<&ActiveScrollbarState>,
9052 window: &mut Window,
9053 ) -> Self {
9054 let ScrollbarLayoutInformation {
9055 editor_bounds,
9056 scroll_range,
9057 glyph_grid_cell,
9058 } = layout_information;
9059
9060 let viewport_size = size(editor_width, editor_bounds.size.height);
9061
9062 let scrollbar_bounds_for = |axis: ScrollbarAxis| match axis {
9063 ScrollbarAxis::Horizontal => Bounds::from_corner_and_size(
9064 Corner::BottomLeft,
9065 editor_bounds.bottom_left(),
9066 size(
9067 // The horizontal viewport size differs from the space available for the
9068 // horizontal scrollbar, so we have to manually stich it together here.
9069 editor_bounds.size.width - right_margin,
9070 scrollbar_width,
9071 ),
9072 ),
9073 ScrollbarAxis::Vertical => Bounds::from_corner_and_size(
9074 Corner::TopRight,
9075 editor_bounds.top_right(),
9076 size(scrollbar_width, viewport_size.height),
9077 ),
9078 };
9079
9080 let mut create_scrollbar_layout = |axis| {
9081 settings_visibility
9082 .along(axis)
9083 .then(|| {
9084 (
9085 viewport_size.along(axis) - content_offset.along(axis),
9086 scroll_range.along(axis),
9087 )
9088 })
9089 .filter(|(viewport_size, scroll_range)| {
9090 // The scrollbar should only be rendered if the content does
9091 // not entirely fit into the editor
9092 // However, this only applies to the horizontal scrollbar, as information about the
9093 // vertical scrollbar layout is always needed for scrollbar diagnostics.
9094 axis != ScrollbarAxis::Horizontal || viewport_size < scroll_range
9095 })
9096 .map(|(viewport_size, scroll_range)| {
9097 ScrollbarLayout::new(
9098 window.insert_hitbox(scrollbar_bounds_for(axis), HitboxBehavior::Normal),
9099 viewport_size,
9100 scroll_range,
9101 glyph_grid_cell.along(axis),
9102 content_offset.along(axis),
9103 scroll_position.along(axis),
9104 show_scrollbars,
9105 axis,
9106 )
9107 .with_thumb_state(
9108 scrollbar_state.and_then(|state| state.thumb_state_for_axis(axis)),
9109 )
9110 })
9111 };
9112
9113 Self {
9114 vertical: create_scrollbar_layout(ScrollbarAxis::Vertical),
9115 horizontal: create_scrollbar_layout(ScrollbarAxis::Horizontal),
9116 visible: show_scrollbars,
9117 }
9118 }
9119
9120 pub fn iter_scrollbars(&self) -> impl Iterator<Item = (&ScrollbarLayout, ScrollbarAxis)> + '_ {
9121 [
9122 (&self.vertical, ScrollbarAxis::Vertical),
9123 (&self.horizontal, ScrollbarAxis::Horizontal),
9124 ]
9125 .into_iter()
9126 .filter_map(|(scrollbar, axis)| scrollbar.as_ref().map(|s| (s, axis)))
9127 }
9128
9129 /// Returns the currently hovered scrollbar axis, if any.
9130 pub fn get_hovered_axis(&self, window: &Window) -> Option<(&ScrollbarLayout, ScrollbarAxis)> {
9131 self.iter_scrollbars()
9132 .find(|s| s.0.hitbox.is_hovered(window))
9133 }
9134}
9135
9136#[derive(Clone)]
9137struct ScrollbarLayout {
9138 hitbox: Hitbox,
9139 visible_range: Range<f32>,
9140 text_unit_size: Pixels,
9141 thumb_bounds: Option<Bounds<Pixels>>,
9142 thumb_state: ScrollbarThumbState,
9143}
9144
9145impl ScrollbarLayout {
9146 const BORDER_WIDTH: Pixels = px(1.0);
9147 const LINE_MARKER_HEIGHT: Pixels = px(2.0);
9148 const MIN_MARKER_HEIGHT: Pixels = px(5.0);
9149 const MIN_THUMB_SIZE: Pixels = px(25.0);
9150
9151 fn new(
9152 scrollbar_track_hitbox: Hitbox,
9153 viewport_size: Pixels,
9154 scroll_range: Pixels,
9155 glyph_space: Pixels,
9156 content_offset: Pixels,
9157 scroll_position: f32,
9158 show_thumb: bool,
9159 axis: ScrollbarAxis,
9160 ) -> Self {
9161 let track_bounds = scrollbar_track_hitbox.bounds;
9162 // The length of the track available to the scrollbar thumb. We deliberately
9163 // exclude the content size here so that the thumb aligns with the content.
9164 let track_length = track_bounds.size.along(axis) - content_offset;
9165
9166 Self::new_with_hitbox_and_track_length(
9167 scrollbar_track_hitbox,
9168 track_length,
9169 viewport_size,
9170 scroll_range,
9171 glyph_space,
9172 content_offset,
9173 scroll_position,
9174 show_thumb,
9175 axis,
9176 )
9177 }
9178
9179 fn for_minimap(
9180 minimap_track_hitbox: Hitbox,
9181 visible_lines: f32,
9182 total_editor_lines: f32,
9183 minimap_line_height: Pixels,
9184 scroll_position: f32,
9185 minimap_scroll_top: f32,
9186 show_thumb: bool,
9187 ) -> Self {
9188 // The scrollbar thumb size is calculated as
9189 // (visible_content/total_content) × scrollbar_track_length.
9190 //
9191 // For the minimap's thumb layout, we leverage this by setting the
9192 // scrollbar track length to the entire document size (using minimap line
9193 // height). This creates a thumb that exactly represents the editor
9194 // viewport scaled to minimap proportions.
9195 //
9196 // We adjust the thumb position relative to `minimap_scroll_top` to
9197 // accommodate for the deliberately oversized track.
9198 //
9199 // This approach ensures that the minimap thumb accurately reflects the
9200 // editor's current scroll position whilst nicely synchronizing the minimap
9201 // thumb and scrollbar thumb.
9202 let scroll_range = total_editor_lines * minimap_line_height;
9203 let viewport_size = visible_lines * minimap_line_height;
9204
9205 let track_top_offset = -minimap_scroll_top * minimap_line_height;
9206
9207 Self::new_with_hitbox_and_track_length(
9208 minimap_track_hitbox,
9209 scroll_range,
9210 viewport_size,
9211 scroll_range,
9212 minimap_line_height,
9213 track_top_offset,
9214 scroll_position,
9215 show_thumb,
9216 ScrollbarAxis::Vertical,
9217 )
9218 }
9219
9220 fn new_with_hitbox_and_track_length(
9221 scrollbar_track_hitbox: Hitbox,
9222 track_length: Pixels,
9223 viewport_size: Pixels,
9224 scroll_range: Pixels,
9225 glyph_space: Pixels,
9226 content_offset: Pixels,
9227 scroll_position: f32,
9228 show_thumb: bool,
9229 axis: ScrollbarAxis,
9230 ) -> Self {
9231 let text_units_per_page = viewport_size / glyph_space;
9232 let visible_range = scroll_position..scroll_position + text_units_per_page;
9233 let total_text_units = scroll_range / glyph_space;
9234
9235 let thumb_percentage = text_units_per_page / total_text_units;
9236 let thumb_size = (track_length * thumb_percentage)
9237 .max(ScrollbarLayout::MIN_THUMB_SIZE)
9238 .min(track_length);
9239
9240 let text_unit_divisor = (total_text_units - text_units_per_page).max(0.);
9241
9242 let content_larger_than_viewport = text_unit_divisor > 0.;
9243
9244 let text_unit_size = if content_larger_than_viewport {
9245 (track_length - thumb_size) / text_unit_divisor
9246 } else {
9247 glyph_space
9248 };
9249
9250 let thumb_bounds = (show_thumb && content_larger_than_viewport).then(|| {
9251 Self::thumb_bounds(
9252 &scrollbar_track_hitbox,
9253 content_offset,
9254 visible_range.start,
9255 text_unit_size,
9256 thumb_size,
9257 axis,
9258 )
9259 });
9260
9261 ScrollbarLayout {
9262 hitbox: scrollbar_track_hitbox,
9263 visible_range,
9264 text_unit_size,
9265 thumb_bounds,
9266 thumb_state: Default::default(),
9267 }
9268 }
9269
9270 fn with_thumb_state(self, thumb_state: Option<ScrollbarThumbState>) -> Self {
9271 if let Some(thumb_state) = thumb_state {
9272 Self {
9273 thumb_state,
9274 ..self
9275 }
9276 } else {
9277 self
9278 }
9279 }
9280
9281 fn thumb_bounds(
9282 scrollbar_track: &Hitbox,
9283 content_offset: Pixels,
9284 visible_range_start: f32,
9285 text_unit_size: Pixels,
9286 thumb_size: Pixels,
9287 axis: ScrollbarAxis,
9288 ) -> Bounds<Pixels> {
9289 let thumb_origin = scrollbar_track.origin.apply_along(axis, |origin| {
9290 origin + content_offset + visible_range_start * text_unit_size
9291 });
9292 Bounds::new(
9293 thumb_origin,
9294 scrollbar_track.size.apply_along(axis, |_| thumb_size),
9295 )
9296 }
9297
9298 fn thumb_hovered(&self, position: &gpui::Point<Pixels>) -> bool {
9299 self.thumb_bounds
9300 .is_some_and(|bounds| bounds.contains(position))
9301 }
9302
9303 fn marker_quads_for_ranges(
9304 &self,
9305 row_ranges: impl IntoIterator<Item = ColoredRange<DisplayRow>>,
9306 column: Option<usize>,
9307 ) -> Vec<PaintQuad> {
9308 struct MinMax {
9309 min: Pixels,
9310 max: Pixels,
9311 }
9312 let (x_range, height_limit) = if let Some(column) = column {
9313 let column_width = px(((self.hitbox.size.width - Self::BORDER_WIDTH).0 / 3.0).floor());
9314 let start = Self::BORDER_WIDTH + (column as f32 * column_width);
9315 let end = start + column_width;
9316 (
9317 Range { start, end },
9318 MinMax {
9319 min: Self::MIN_MARKER_HEIGHT,
9320 max: px(f32::MAX),
9321 },
9322 )
9323 } else {
9324 (
9325 Range {
9326 start: Self::BORDER_WIDTH,
9327 end: self.hitbox.size.width,
9328 },
9329 MinMax {
9330 min: Self::LINE_MARKER_HEIGHT,
9331 max: Self::LINE_MARKER_HEIGHT,
9332 },
9333 )
9334 };
9335
9336 let row_to_y = |row: DisplayRow| row.as_f32() * self.text_unit_size;
9337 let mut pixel_ranges = row_ranges
9338 .into_iter()
9339 .map(|range| {
9340 let start_y = row_to_y(range.start);
9341 let end_y = row_to_y(range.end)
9342 + self
9343 .text_unit_size
9344 .max(height_limit.min)
9345 .min(height_limit.max);
9346 ColoredRange {
9347 start: start_y,
9348 end: end_y,
9349 color: range.color,
9350 }
9351 })
9352 .peekable();
9353
9354 let mut quads = Vec::new();
9355 while let Some(mut pixel_range) = pixel_ranges.next() {
9356 while let Some(next_pixel_range) = pixel_ranges.peek() {
9357 if pixel_range.end >= next_pixel_range.start - px(1.0)
9358 && pixel_range.color == next_pixel_range.color
9359 {
9360 pixel_range.end = next_pixel_range.end.max(pixel_range.end);
9361 pixel_ranges.next();
9362 } else {
9363 break;
9364 }
9365 }
9366
9367 let bounds = Bounds::from_corners(
9368 point(x_range.start, pixel_range.start),
9369 point(x_range.end, pixel_range.end),
9370 );
9371 quads.push(quad(
9372 bounds,
9373 Corners::default(),
9374 pixel_range.color,
9375 Edges::default(),
9376 Hsla::transparent_black(),
9377 BorderStyle::default(),
9378 ));
9379 }
9380
9381 quads
9382 }
9383}
9384
9385struct MinimapLayout {
9386 pub minimap: AnyElement,
9387 pub thumb_layout: ScrollbarLayout,
9388 pub minimap_scroll_top: f32,
9389 pub minimap_line_height: Pixels,
9390 pub thumb_border_style: MinimapThumbBorder,
9391 pub max_scroll_top: f32,
9392}
9393
9394impl MinimapLayout {
9395 const MINIMAP_WIDTH: Pixels = px(100.);
9396 /// Calculates the scroll top offset the minimap editor has to have based on the
9397 /// current scroll progress.
9398 fn calculate_minimap_top_offset(
9399 document_lines: f32,
9400 visible_editor_lines: f32,
9401 visible_minimap_lines: f32,
9402 scroll_position: f32,
9403 ) -> f32 {
9404 let non_visible_document_lines = (document_lines - visible_editor_lines).max(0.);
9405 if non_visible_document_lines == 0. {
9406 0.
9407 } else {
9408 let scroll_percentage = (scroll_position / non_visible_document_lines).clamp(0., 1.);
9409 scroll_percentage * (document_lines - visible_minimap_lines).max(0.)
9410 }
9411 }
9412}
9413
9414struct CreaseTrailerLayout {
9415 element: AnyElement,
9416 bounds: Bounds<Pixels>,
9417}
9418
9419pub(crate) struct PositionMap {
9420 pub size: Size<Pixels>,
9421 pub line_height: Pixels,
9422 pub scroll_pixel_position: gpui::Point<Pixels>,
9423 pub scroll_max: gpui::Point<f32>,
9424 pub em_width: Pixels,
9425 pub em_advance: Pixels,
9426 pub visible_row_range: Range<DisplayRow>,
9427 pub line_layouts: Vec<LineWithInvisibles>,
9428 pub snapshot: EditorSnapshot,
9429 pub text_hitbox: Hitbox,
9430 pub gutter_hitbox: Hitbox,
9431 pub inline_blame_bounds: Option<(Bounds<Pixels>, BlameEntry)>,
9432 pub display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
9433 pub diff_hunk_control_bounds: Vec<(DisplayRow, Bounds<Pixels>)>,
9434}
9435
9436#[derive(Debug, Copy, Clone)]
9437pub struct PointForPosition {
9438 pub previous_valid: DisplayPoint,
9439 pub next_valid: DisplayPoint,
9440 pub exact_unclipped: DisplayPoint,
9441 pub column_overshoot_after_line_end: u32,
9442}
9443
9444impl PointForPosition {
9445 pub fn as_valid(&self) -> Option<DisplayPoint> {
9446 if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped {
9447 Some(self.previous_valid)
9448 } else {
9449 None
9450 }
9451 }
9452
9453 pub fn intersects_selection(&self, selection: &Selection<DisplayPoint>) -> bool {
9454 let Some(valid_point) = self.as_valid() else {
9455 return false;
9456 };
9457 let range = selection.range();
9458
9459 let candidate_row = valid_point.row();
9460 let candidate_col = valid_point.column();
9461
9462 let start_row = range.start.row();
9463 let start_col = range.start.column();
9464 let end_row = range.end.row();
9465 let end_col = range.end.column();
9466
9467 if candidate_row < start_row || candidate_row > end_row {
9468 false
9469 } else if start_row == end_row {
9470 candidate_col >= start_col && candidate_col < end_col
9471 } else {
9472 if candidate_row == start_row {
9473 candidate_col >= start_col
9474 } else if candidate_row == end_row {
9475 candidate_col < end_col
9476 } else {
9477 true
9478 }
9479 }
9480 }
9481}
9482
9483impl PositionMap {
9484 pub(crate) fn point_for_position(&self, position: gpui::Point<Pixels>) -> PointForPosition {
9485 let text_bounds = self.text_hitbox.bounds;
9486 let scroll_position = self.snapshot.scroll_position();
9487 let position = position - text_bounds.origin;
9488 let y = position.y.max(px(0.)).min(self.size.height);
9489 let x = position.x + (scroll_position.x * self.em_advance);
9490 let row = ((y / self.line_height) + scroll_position.y) as u32;
9491
9492 let (column, x_overshoot_after_line_end) = if let Some(line) = self
9493 .line_layouts
9494 .get(row as usize - scroll_position.y as usize)
9495 {
9496 if let Some(ix) = line.index_for_x(x) {
9497 (ix as u32, px(0.))
9498 } else {
9499 (line.len as u32, px(0.).max(x - line.width))
9500 }
9501 } else {
9502 (0, x)
9503 };
9504
9505 let mut exact_unclipped = DisplayPoint::new(DisplayRow(row), column);
9506 let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
9507 let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
9508
9509 let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32;
9510 *exact_unclipped.column_mut() += column_overshoot_after_line_end;
9511 PointForPosition {
9512 previous_valid,
9513 next_valid,
9514 exact_unclipped,
9515 column_overshoot_after_line_end,
9516 }
9517 }
9518}
9519
9520struct BlockLayout {
9521 id: BlockId,
9522 x_offset: Pixels,
9523 row: Option<DisplayRow>,
9524 element: AnyElement,
9525 available_space: Size<AvailableSpace>,
9526 style: BlockStyle,
9527 overlaps_gutter: bool,
9528 is_buffer_header: bool,
9529}
9530
9531pub fn layout_line(
9532 row: DisplayRow,
9533 snapshot: &EditorSnapshot,
9534 style: &EditorStyle,
9535 text_width: Pixels,
9536 is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
9537 window: &mut Window,
9538 cx: &mut App,
9539) -> LineWithInvisibles {
9540 let chunks = snapshot.highlighted_chunks(row..row + DisplayRow(1), true, style);
9541 LineWithInvisibles::from_chunks(
9542 chunks,
9543 &style,
9544 MAX_LINE_LEN,
9545 1,
9546 &snapshot.mode,
9547 text_width,
9548 is_row_soft_wrapped,
9549 window,
9550 cx,
9551 )
9552 .pop()
9553 .unwrap()
9554}
9555
9556#[derive(Debug)]
9557pub struct IndentGuideLayout {
9558 origin: gpui::Point<Pixels>,
9559 length: Pixels,
9560 single_indent_width: Pixels,
9561 depth: u32,
9562 active: bool,
9563 settings: IndentGuideSettings,
9564}
9565
9566pub struct CursorLayout {
9567 origin: gpui::Point<Pixels>,
9568 block_width: Pixels,
9569 line_height: Pixels,
9570 color: Hsla,
9571 shape: CursorShape,
9572 block_text: Option<ShapedLine>,
9573 cursor_name: Option<AnyElement>,
9574}
9575
9576#[derive(Debug)]
9577pub struct CursorName {
9578 string: SharedString,
9579 color: Hsla,
9580 is_top_row: bool,
9581}
9582
9583impl CursorLayout {
9584 pub fn new(
9585 origin: gpui::Point<Pixels>,
9586 block_width: Pixels,
9587 line_height: Pixels,
9588 color: Hsla,
9589 shape: CursorShape,
9590 block_text: Option<ShapedLine>,
9591 ) -> CursorLayout {
9592 CursorLayout {
9593 origin,
9594 block_width,
9595 line_height,
9596 color,
9597 shape,
9598 block_text,
9599 cursor_name: None,
9600 }
9601 }
9602
9603 pub fn bounding_rect(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
9604 Bounds {
9605 origin: self.origin + origin,
9606 size: size(self.block_width, self.line_height),
9607 }
9608 }
9609
9610 fn bounds(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
9611 match self.shape {
9612 CursorShape::Bar => Bounds {
9613 origin: self.origin + origin,
9614 size: size(px(2.0), self.line_height),
9615 },
9616 CursorShape::Block | CursorShape::Hollow => Bounds {
9617 origin: self.origin + origin,
9618 size: size(self.block_width, self.line_height),
9619 },
9620 CursorShape::Underline => Bounds {
9621 origin: self.origin
9622 + origin
9623 + gpui::Point::new(Pixels::ZERO, self.line_height - px(2.0)),
9624 size: size(self.block_width, px(2.0)),
9625 },
9626 }
9627 }
9628
9629 pub fn layout(
9630 &mut self,
9631 origin: gpui::Point<Pixels>,
9632 cursor_name: Option<CursorName>,
9633 window: &mut Window,
9634 cx: &mut App,
9635 ) {
9636 if let Some(cursor_name) = cursor_name {
9637 let bounds = self.bounds(origin);
9638 let text_size = self.line_height / 1.5;
9639
9640 let name_origin = if cursor_name.is_top_row {
9641 point(bounds.right() - px(1.), bounds.top())
9642 } else {
9643 match self.shape {
9644 CursorShape::Bar => point(
9645 bounds.right() - px(2.),
9646 bounds.top() - text_size / 2. - px(1.),
9647 ),
9648 _ => point(
9649 bounds.right() - px(1.),
9650 bounds.top() - text_size / 2. - px(1.),
9651 ),
9652 }
9653 };
9654 let mut name_element = div()
9655 .bg(self.color)
9656 .text_size(text_size)
9657 .px_0p5()
9658 .line_height(text_size + px(2.))
9659 .text_color(cursor_name.color)
9660 .child(cursor_name.string.clone())
9661 .into_any_element();
9662
9663 name_element.prepaint_as_root(name_origin, AvailableSpace::min_size(), window, cx);
9664
9665 self.cursor_name = Some(name_element);
9666 }
9667 }
9668
9669 pub fn paint(&mut self, origin: gpui::Point<Pixels>, window: &mut Window, cx: &mut App) {
9670 let bounds = self.bounds(origin);
9671
9672 //Draw background or border quad
9673 let cursor = if matches!(self.shape, CursorShape::Hollow) {
9674 outline(bounds, self.color, BorderStyle::Solid)
9675 } else {
9676 fill(bounds, self.color)
9677 };
9678
9679 if let Some(name) = &mut self.cursor_name {
9680 name.paint(window, cx);
9681 }
9682
9683 window.paint_quad(cursor);
9684
9685 if let Some(block_text) = &self.block_text {
9686 block_text
9687 .paint(self.origin + origin, self.line_height, window, cx)
9688 .log_err();
9689 }
9690 }
9691
9692 pub fn shape(&self) -> CursorShape {
9693 self.shape
9694 }
9695}
9696
9697#[derive(Debug)]
9698pub struct HighlightedRange {
9699 pub start_y: Pixels,
9700 pub line_height: Pixels,
9701 pub lines: Vec<HighlightedRangeLine>,
9702 pub color: Hsla,
9703 pub corner_radius: Pixels,
9704}
9705
9706#[derive(Debug)]
9707pub struct HighlightedRangeLine {
9708 pub start_x: Pixels,
9709 pub end_x: Pixels,
9710}
9711
9712impl HighlightedRange {
9713 pub fn paint(&self, bounds: Bounds<Pixels>, window: &mut Window) {
9714 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
9715 self.paint_lines(self.start_y, &self.lines[0..1], bounds, window);
9716 self.paint_lines(
9717 self.start_y + self.line_height,
9718 &self.lines[1..],
9719 bounds,
9720 window,
9721 );
9722 } else {
9723 self.paint_lines(self.start_y, &self.lines, bounds, window);
9724 }
9725 }
9726
9727 fn paint_lines(
9728 &self,
9729 start_y: Pixels,
9730 lines: &[HighlightedRangeLine],
9731 _bounds: Bounds<Pixels>,
9732 window: &mut Window,
9733 ) {
9734 if lines.is_empty() {
9735 return;
9736 }
9737
9738 let first_line = lines.first().unwrap();
9739 let last_line = lines.last().unwrap();
9740
9741 let first_top_left = point(first_line.start_x, start_y);
9742 let first_top_right = point(first_line.end_x, start_y);
9743
9744 let curve_height = point(Pixels::ZERO, self.corner_radius);
9745 let curve_width = |start_x: Pixels, end_x: Pixels| {
9746 let max = (end_x - start_x) / 2.;
9747 let width = if max < self.corner_radius {
9748 max
9749 } else {
9750 self.corner_radius
9751 };
9752
9753 point(width, Pixels::ZERO)
9754 };
9755
9756 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
9757 let mut builder = gpui::PathBuilder::fill();
9758 builder.move_to(first_top_right - top_curve_width);
9759 builder.curve_to(first_top_right + curve_height, first_top_right);
9760
9761 let mut iter = lines.iter().enumerate().peekable();
9762 while let Some((ix, line)) = iter.next() {
9763 let bottom_right = point(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
9764
9765 if let Some((_, next_line)) = iter.peek() {
9766 let next_top_right = point(next_line.end_x, bottom_right.y);
9767
9768 match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
9769 Ordering::Equal => {
9770 builder.line_to(bottom_right);
9771 }
9772 Ordering::Less => {
9773 let curve_width = curve_width(next_top_right.x, bottom_right.x);
9774 builder.line_to(bottom_right - curve_height);
9775 if self.corner_radius > Pixels::ZERO {
9776 builder.curve_to(bottom_right - curve_width, bottom_right);
9777 }
9778 builder.line_to(next_top_right + curve_width);
9779 if self.corner_radius > Pixels::ZERO {
9780 builder.curve_to(next_top_right + curve_height, next_top_right);
9781 }
9782 }
9783 Ordering::Greater => {
9784 let curve_width = curve_width(bottom_right.x, next_top_right.x);
9785 builder.line_to(bottom_right - curve_height);
9786 if self.corner_radius > Pixels::ZERO {
9787 builder.curve_to(bottom_right + curve_width, bottom_right);
9788 }
9789 builder.line_to(next_top_right - curve_width);
9790 if self.corner_radius > Pixels::ZERO {
9791 builder.curve_to(next_top_right + curve_height, next_top_right);
9792 }
9793 }
9794 }
9795 } else {
9796 let curve_width = curve_width(line.start_x, line.end_x);
9797 builder.line_to(bottom_right - curve_height);
9798 if self.corner_radius > Pixels::ZERO {
9799 builder.curve_to(bottom_right - curve_width, bottom_right);
9800 }
9801
9802 let bottom_left = point(line.start_x, bottom_right.y);
9803 builder.line_to(bottom_left + curve_width);
9804 if self.corner_radius > Pixels::ZERO {
9805 builder.curve_to(bottom_left - curve_height, bottom_left);
9806 }
9807 }
9808 }
9809
9810 if first_line.start_x > last_line.start_x {
9811 let curve_width = curve_width(last_line.start_x, first_line.start_x);
9812 let second_top_left = point(last_line.start_x, start_y + self.line_height);
9813 builder.line_to(second_top_left + curve_height);
9814 if self.corner_radius > Pixels::ZERO {
9815 builder.curve_to(second_top_left + curve_width, second_top_left);
9816 }
9817 let first_bottom_left = point(first_line.start_x, second_top_left.y);
9818 builder.line_to(first_bottom_left - curve_width);
9819 if self.corner_radius > Pixels::ZERO {
9820 builder.curve_to(first_bottom_left - curve_height, first_bottom_left);
9821 }
9822 }
9823
9824 builder.line_to(first_top_left + curve_height);
9825 if self.corner_radius > Pixels::ZERO {
9826 builder.curve_to(first_top_left + top_curve_width, first_top_left);
9827 }
9828 builder.line_to(first_top_right - top_curve_width);
9829
9830 if let Ok(path) = builder.build() {
9831 window.paint_path(path, self.color);
9832 }
9833 }
9834}
9835
9836enum CursorPopoverType {
9837 CodeContextMenu,
9838 EditPrediction,
9839}
9840
9841pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
9842 (delta.pow(1.2) / 100.0).min(px(3.0)).into()
9843}
9844
9845fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
9846 (delta.pow(1.2) / 300.0).into()
9847}
9848
9849pub fn register_action<T: Action>(
9850 editor: &Entity<Editor>,
9851 window: &mut Window,
9852 listener: impl Fn(&mut Editor, &T, &mut Window, &mut Context<Editor>) + 'static,
9853) {
9854 let editor = editor.clone();
9855 window.on_action(TypeId::of::<T>(), move |action, phase, window, cx| {
9856 let action = action.downcast_ref().unwrap();
9857 if phase == DispatchPhase::Bubble {
9858 editor.update(cx, |editor, cx| {
9859 listener(editor, action, window, cx);
9860 })
9861 }
9862 })
9863}
9864
9865fn compute_auto_height_layout(
9866 editor: &mut Editor,
9867 max_lines: usize,
9868 max_line_number_width: Pixels,
9869 known_dimensions: Size<Option<Pixels>>,
9870 available_width: AvailableSpace,
9871 window: &mut Window,
9872 cx: &mut Context<Editor>,
9873) -> Option<Size<Pixels>> {
9874 let width = known_dimensions.width.or({
9875 if let AvailableSpace::Definite(available_width) = available_width {
9876 Some(available_width)
9877 } else {
9878 None
9879 }
9880 })?;
9881 if let Some(height) = known_dimensions.height {
9882 return Some(size(width, height));
9883 }
9884
9885 let style = editor.style.as_ref().unwrap();
9886 let font_id = window.text_system().resolve_font(&style.text.font());
9887 let font_size = style.text.font_size.to_pixels(window.rem_size());
9888 let line_height = style.text.line_height_in_pixels(window.rem_size());
9889 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
9890
9891 let mut snapshot = editor.snapshot(window, cx);
9892 let gutter_dimensions = snapshot
9893 .gutter_dimensions(font_id, font_size, max_line_number_width, cx)
9894 .or_else(|| {
9895 editor
9896 .offset_content
9897 .then(|| GutterDimensions::default_with_margin(font_id, font_size, cx))
9898 })
9899 .unwrap_or_default();
9900
9901 editor.gutter_dimensions = gutter_dimensions;
9902 let text_width = width - gutter_dimensions.width;
9903 let overscroll = size(em_width, px(0.));
9904
9905 let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width;
9906 if !matches!(editor.soft_wrap_mode(cx), SoftWrap::None) {
9907 if editor.set_wrap_width(Some(editor_width), cx) {
9908 snapshot = editor.snapshot(window, cx);
9909 }
9910 }
9911
9912 let scroll_height = (snapshot.max_point().row().next_row().0 as f32) * line_height;
9913 let height = scroll_height
9914 .max(line_height)
9915 .min(line_height * max_lines as f32);
9916
9917 Some(size(width, height))
9918}
9919
9920#[cfg(test)]
9921mod tests {
9922 use super::*;
9923 use crate::{
9924 Editor, MultiBuffer,
9925 display_map::{BlockPlacement, BlockProperties},
9926 editor_tests::{init_test, update_test_language_settings},
9927 };
9928 use gpui::{TestAppContext, VisualTestContext};
9929 use language::language_settings;
9930 use log::info;
9931 use std::num::NonZeroU32;
9932 use util::test::sample_text;
9933
9934 #[gpui::test]
9935 fn test_shape_line_numbers(cx: &mut TestAppContext) {
9936 init_test(cx, |_| {});
9937 let window = cx.add_window(|window, cx| {
9938 let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
9939 Editor::new(EditorMode::full(), buffer, None, window, cx)
9940 });
9941
9942 let editor = window.root(cx).unwrap();
9943 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
9944 let line_height = window
9945 .update(cx, |_, window, _| {
9946 style.text.line_height_in_pixels(window.rem_size())
9947 })
9948 .unwrap();
9949 let element = EditorElement::new(&editor, style);
9950 let snapshot = window
9951 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
9952 .unwrap();
9953
9954 let layouts = cx
9955 .update_window(*window, |_, window, cx| {
9956 element.layout_line_numbers(
9957 None,
9958 GutterDimensions {
9959 left_padding: Pixels::ZERO,
9960 right_padding: Pixels::ZERO,
9961 width: px(30.0),
9962 margin: Pixels::ZERO,
9963 git_blame_entries_width: None,
9964 },
9965 line_height,
9966 gpui::Point::default(),
9967 DisplayRow(0)..DisplayRow(6),
9968 &(0..6)
9969 .map(|row| RowInfo {
9970 buffer_row: Some(row),
9971 ..Default::default()
9972 })
9973 .collect::<Vec<_>>(),
9974 &BTreeMap::default(),
9975 Some(DisplayPoint::new(DisplayRow(0), 0)),
9976 &snapshot,
9977 window,
9978 cx,
9979 )
9980 })
9981 .unwrap();
9982 assert_eq!(layouts.len(), 6);
9983
9984 let relative_rows = window
9985 .update(cx, |editor, window, cx| {
9986 let snapshot = editor.snapshot(window, cx);
9987 element.calculate_relative_line_numbers(
9988 &snapshot,
9989 &(DisplayRow(0)..DisplayRow(6)),
9990 Some(DisplayRow(3)),
9991 )
9992 })
9993 .unwrap();
9994 assert_eq!(relative_rows[&DisplayRow(0)], 3);
9995 assert_eq!(relative_rows[&DisplayRow(1)], 2);
9996 assert_eq!(relative_rows[&DisplayRow(2)], 1);
9997 // current line has no relative number
9998 assert_eq!(relative_rows[&DisplayRow(4)], 1);
9999 assert_eq!(relative_rows[&DisplayRow(5)], 2);
10000
10001 // works if cursor is before screen
10002 let relative_rows = window
10003 .update(cx, |editor, window, cx| {
10004 let snapshot = editor.snapshot(window, cx);
10005 element.calculate_relative_line_numbers(
10006 &snapshot,
10007 &(DisplayRow(3)..DisplayRow(6)),
10008 Some(DisplayRow(1)),
10009 )
10010 })
10011 .unwrap();
10012 assert_eq!(relative_rows.len(), 3);
10013 assert_eq!(relative_rows[&DisplayRow(3)], 2);
10014 assert_eq!(relative_rows[&DisplayRow(4)], 3);
10015 assert_eq!(relative_rows[&DisplayRow(5)], 4);
10016
10017 // works if cursor is after screen
10018 let relative_rows = window
10019 .update(cx, |editor, window, cx| {
10020 let snapshot = editor.snapshot(window, cx);
10021 element.calculate_relative_line_numbers(
10022 &snapshot,
10023 &(DisplayRow(0)..DisplayRow(3)),
10024 Some(DisplayRow(6)),
10025 )
10026 })
10027 .unwrap();
10028 assert_eq!(relative_rows.len(), 3);
10029 assert_eq!(relative_rows[&DisplayRow(0)], 5);
10030 assert_eq!(relative_rows[&DisplayRow(1)], 4);
10031 assert_eq!(relative_rows[&DisplayRow(2)], 3);
10032 }
10033
10034 #[gpui::test]
10035 async fn test_vim_visual_selections(cx: &mut TestAppContext) {
10036 init_test(cx, |_| {});
10037
10038 let window = cx.add_window(|window, cx| {
10039 let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
10040 Editor::new(EditorMode::full(), buffer, None, window, cx)
10041 });
10042 let cx = &mut VisualTestContext::from_window(*window, cx);
10043 let editor = window.root(cx).unwrap();
10044 let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
10045
10046 window
10047 .update(cx, |editor, window, cx| {
10048 editor.cursor_shape = CursorShape::Block;
10049 editor.change_selections(None, window, cx, |s| {
10050 s.select_ranges([
10051 Point::new(0, 0)..Point::new(1, 0),
10052 Point::new(3, 2)..Point::new(3, 3),
10053 Point::new(5, 6)..Point::new(6, 0),
10054 ]);
10055 });
10056 })
10057 .unwrap();
10058
10059 let (_, state) = cx.draw(
10060 point(px(500.), px(500.)),
10061 size(px(500.), px(500.)),
10062 |_, _| EditorElement::new(&editor, style),
10063 );
10064
10065 assert_eq!(state.selections.len(), 1);
10066 let local_selections = &state.selections[0].1;
10067 assert_eq!(local_selections.len(), 3);
10068 // moves cursor back one line
10069 assert_eq!(
10070 local_selections[0].head,
10071 DisplayPoint::new(DisplayRow(0), 6)
10072 );
10073 assert_eq!(
10074 local_selections[0].range,
10075 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0)
10076 );
10077
10078 // moves cursor back one column
10079 assert_eq!(
10080 local_selections[1].range,
10081 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 3)
10082 );
10083 assert_eq!(
10084 local_selections[1].head,
10085 DisplayPoint::new(DisplayRow(3), 2)
10086 );
10087
10088 // leaves cursor on the max point
10089 assert_eq!(
10090 local_selections[2].range,
10091 DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(6), 0)
10092 );
10093 assert_eq!(
10094 local_selections[2].head,
10095 DisplayPoint::new(DisplayRow(6), 0)
10096 );
10097
10098 // active lines does not include 1 (even though the range of the selection does)
10099 assert_eq!(
10100 state.active_rows.keys().cloned().collect::<Vec<_>>(),
10101 vec![DisplayRow(0), DisplayRow(3), DisplayRow(5), DisplayRow(6)]
10102 );
10103 }
10104
10105 #[gpui::test]
10106 fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
10107 init_test(cx, |_| {});
10108
10109 let window = cx.add_window(|window, cx| {
10110 let buffer = MultiBuffer::build_simple("", cx);
10111 Editor::new(EditorMode::full(), buffer, None, window, cx)
10112 });
10113 let cx = &mut VisualTestContext::from_window(*window, cx);
10114 let editor = window.root(cx).unwrap();
10115 let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
10116 window
10117 .update(cx, |editor, window, cx| {
10118 editor.set_placeholder_text("hello", cx);
10119 editor.insert_blocks(
10120 [BlockProperties {
10121 style: BlockStyle::Fixed,
10122 placement: BlockPlacement::Above(Anchor::min()),
10123 height: Some(3),
10124 render: Arc::new(|cx| div().h(3. * cx.window.line_height()).into_any()),
10125 priority: 0,
10126 render_in_minimap: true,
10127 }],
10128 None,
10129 cx,
10130 );
10131
10132 // Blur the editor so that it displays placeholder text.
10133 window.blur();
10134 })
10135 .unwrap();
10136
10137 let (_, state) = cx.draw(
10138 point(px(500.), px(500.)),
10139 size(px(500.), px(500.)),
10140 |_, _| EditorElement::new(&editor, style),
10141 );
10142 assert_eq!(state.position_map.line_layouts.len(), 4);
10143 assert_eq!(state.line_numbers.len(), 1);
10144 assert_eq!(
10145 state
10146 .line_numbers
10147 .get(&MultiBufferRow(0))
10148 .map(|line_number| line_number.shaped_line.text.as_ref()),
10149 Some("1")
10150 );
10151 }
10152
10153 #[gpui::test]
10154 fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
10155 const TAB_SIZE: u32 = 4;
10156
10157 let input_text = "\t \t|\t| a b";
10158 let expected_invisibles = vec![
10159 Invisible::Tab {
10160 line_start_offset: 0,
10161 line_end_offset: TAB_SIZE as usize,
10162 },
10163 Invisible::Whitespace {
10164 line_offset: TAB_SIZE as usize,
10165 },
10166 Invisible::Tab {
10167 line_start_offset: TAB_SIZE as usize + 1,
10168 line_end_offset: TAB_SIZE as usize * 2,
10169 },
10170 Invisible::Tab {
10171 line_start_offset: TAB_SIZE as usize * 2 + 1,
10172 line_end_offset: TAB_SIZE as usize * 3,
10173 },
10174 Invisible::Whitespace {
10175 line_offset: TAB_SIZE as usize * 3 + 1,
10176 },
10177 Invisible::Whitespace {
10178 line_offset: TAB_SIZE as usize * 3 + 3,
10179 },
10180 ];
10181 assert_eq!(
10182 expected_invisibles.len(),
10183 input_text
10184 .chars()
10185 .filter(|initial_char| initial_char.is_whitespace())
10186 .count(),
10187 "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
10188 );
10189
10190 for show_line_numbers in [true, false] {
10191 init_test(cx, |s| {
10192 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
10193 s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
10194 });
10195
10196 let actual_invisibles = collect_invisibles_from_new_editor(
10197 cx,
10198 EditorMode::full(),
10199 input_text,
10200 px(500.0),
10201 show_line_numbers,
10202 );
10203
10204 assert_eq!(expected_invisibles, actual_invisibles);
10205 }
10206 }
10207
10208 #[gpui::test]
10209 fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
10210 init_test(cx, |s| {
10211 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
10212 s.defaults.tab_size = NonZeroU32::new(4);
10213 });
10214
10215 for editor_mode_without_invisibles in [
10216 EditorMode::SingleLine { auto_width: false },
10217 EditorMode::AutoHeight { max_lines: 100 },
10218 ] {
10219 for show_line_numbers in [true, false] {
10220 let invisibles = collect_invisibles_from_new_editor(
10221 cx,
10222 editor_mode_without_invisibles.clone(),
10223 "\t\t\t| | a b",
10224 px(500.0),
10225 show_line_numbers,
10226 );
10227 assert!(
10228 invisibles.is_empty(),
10229 "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}"
10230 );
10231 }
10232 }
10233 }
10234
10235 #[gpui::test]
10236 fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
10237 let tab_size = 4;
10238 let input_text = "a\tbcd ".repeat(9);
10239 let repeated_invisibles = [
10240 Invisible::Tab {
10241 line_start_offset: 1,
10242 line_end_offset: tab_size as usize,
10243 },
10244 Invisible::Whitespace {
10245 line_offset: tab_size as usize + 3,
10246 },
10247 Invisible::Whitespace {
10248 line_offset: tab_size as usize + 4,
10249 },
10250 Invisible::Whitespace {
10251 line_offset: tab_size as usize + 5,
10252 },
10253 Invisible::Whitespace {
10254 line_offset: tab_size as usize + 6,
10255 },
10256 Invisible::Whitespace {
10257 line_offset: tab_size as usize + 7,
10258 },
10259 ];
10260 let expected_invisibles = std::iter::once(repeated_invisibles)
10261 .cycle()
10262 .take(9)
10263 .flatten()
10264 .collect::<Vec<_>>();
10265 assert_eq!(
10266 expected_invisibles.len(),
10267 input_text
10268 .chars()
10269 .filter(|initial_char| initial_char.is_whitespace())
10270 .count(),
10271 "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
10272 );
10273 info!("Expected invisibles: {expected_invisibles:?}");
10274
10275 init_test(cx, |_| {});
10276
10277 // Put the same string with repeating whitespace pattern into editors of various size,
10278 // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
10279 let resize_step = 10.0;
10280 let mut editor_width = 200.0;
10281 while editor_width <= 1000.0 {
10282 for show_line_numbers in [true, false] {
10283 update_test_language_settings(cx, |s| {
10284 s.defaults.tab_size = NonZeroU32::new(tab_size);
10285 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
10286 s.defaults.preferred_line_length = Some(editor_width as u32);
10287 s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
10288 });
10289
10290 let actual_invisibles = collect_invisibles_from_new_editor(
10291 cx,
10292 EditorMode::full(),
10293 &input_text,
10294 px(editor_width),
10295 show_line_numbers,
10296 );
10297
10298 // Whatever the editor size is, ensure it has the same invisible kinds in the same order
10299 // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
10300 let mut i = 0;
10301 for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
10302 i = actual_index;
10303 match expected_invisibles.get(i) {
10304 Some(expected_invisible) => match (expected_invisible, actual_invisible) {
10305 (Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
10306 | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
10307 _ => {
10308 panic!(
10309 "At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}"
10310 )
10311 }
10312 },
10313 None => {
10314 panic!("Unexpected extra invisible {actual_invisible:?} at index {i}")
10315 }
10316 }
10317 }
10318 let missing_expected_invisibles = &expected_invisibles[i + 1..];
10319 assert!(
10320 missing_expected_invisibles.is_empty(),
10321 "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
10322 );
10323
10324 editor_width += resize_step;
10325 }
10326 }
10327 }
10328
10329 fn collect_invisibles_from_new_editor(
10330 cx: &mut TestAppContext,
10331 editor_mode: EditorMode,
10332 input_text: &str,
10333 editor_width: Pixels,
10334 show_line_numbers: bool,
10335 ) -> Vec<Invisible> {
10336 info!(
10337 "Creating editor with mode {editor_mode:?}, width {}px and text '{input_text}'",
10338 editor_width.0
10339 );
10340 let window = cx.add_window(|window, cx| {
10341 let buffer = MultiBuffer::build_simple(input_text, cx);
10342 Editor::new(editor_mode, buffer, None, window, cx)
10343 });
10344 let cx = &mut VisualTestContext::from_window(*window, cx);
10345 let editor = window.root(cx).unwrap();
10346
10347 let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
10348 window
10349 .update(cx, |editor, _, cx| {
10350 editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
10351 editor.set_wrap_width(Some(editor_width), cx);
10352 editor.set_show_line_numbers(show_line_numbers, cx);
10353 })
10354 .unwrap();
10355 let (_, state) = cx.draw(
10356 point(px(500.), px(500.)),
10357 size(px(500.), px(500.)),
10358 |_, _| EditorElement::new(&editor, style),
10359 );
10360 state
10361 .position_map
10362 .line_layouts
10363 .iter()
10364 .flat_map(|line_with_invisibles| &line_with_invisibles.invisibles)
10365 .cloned()
10366 .collect()
10367 }
10368}