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