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