1use crate::{
2 blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
3 display_map::{
4 BlockContext, BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, ToDisplayPoint,
5 TransformBlock,
6 },
7 editor_settings::{
8 CurrentLineHighlight, DoubleClickInMultibuffer, MultiCursorModifier, ShowScrollbar,
9 },
10 git::{
11 blame::{CommitDetails, GitBlame},
12 diff_hunk_to_display, DisplayDiffHunk,
13 },
14 hover_popover::{
15 self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
16 },
17 hunk_status,
18 items::BufferSearchHighlights,
19 mouse_context_menu::{self, MouseContextMenu},
20 scroll::scroll_amount::ScrollAmount,
21 CodeActionsMenu, CursorShape, DisplayPoint, DisplayRow, DocumentHighlightRead,
22 DocumentHighlightWrite, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
23 ExpandExcerpts, GutterDimensions, HalfPageDown, HalfPageUp, HoveredCursor, HunkToExpand,
24 LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase,
25 Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
26};
27use anyhow::Result;
28use client::ParticipantIndex;
29use collections::{BTreeMap, HashMap};
30use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
31use gpui::{
32 anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
33 transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
34 ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity,
35 GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, ModifiersChangedEvent,
36 MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels,
37 ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, Stateful,
38 StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
39 ViewContext, WeakView, WindowContext,
40};
41use itertools::Itertools;
42use language::language_settings::ShowWhitespaceSetting;
43use lsp::DiagnosticSeverity;
44use multi_buffer::{Anchor, MultiBufferPoint, MultiBufferRow};
45use project::{
46 project_settings::{GitGutterSetting, ProjectSettings},
47 ProjectPath,
48};
49use settings::Settings;
50use smallvec::SmallVec;
51use std::{
52 any::TypeId,
53 borrow::Cow,
54 cmp::{self, max, Ordering},
55 fmt::Write,
56 iter, mem,
57 ops::{Deref, Range},
58 sync::Arc,
59};
60use sum_tree::Bias;
61use theme::{ActiveTheme, PlayerColor};
62use ui::prelude::*;
63use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip};
64use util::ResultExt;
65use workspace::{item::Item, Workspace};
66
67struct SelectionLayout {
68 head: DisplayPoint,
69 cursor_shape: CursorShape,
70 is_newest: bool,
71 is_local: bool,
72 range: Range<DisplayPoint>,
73 active_rows: Range<DisplayRow>,
74 user_name: Option<SharedString>,
75}
76
77impl SelectionLayout {
78 fn new<T: ToPoint + ToDisplayPoint + Clone>(
79 selection: Selection<T>,
80 line_mode: bool,
81 cursor_shape: CursorShape,
82 map: &DisplaySnapshot,
83 is_newest: bool,
84 is_local: bool,
85 user_name: Option<SharedString>,
86 ) -> Self {
87 let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
88 let display_selection = point_selection.map(|p| p.to_display_point(map));
89 let mut range = display_selection.range();
90 let mut head = display_selection.head();
91 let mut active_rows = map.prev_line_boundary(point_selection.start).1.row()
92 ..map.next_line_boundary(point_selection.end).1.row();
93
94 // vim visual line mode
95 if line_mode {
96 let point_range = map.expand_to_line(point_selection.range());
97 range = point_range.start.to_display_point(map)..point_range.end.to_display_point(map);
98 }
99
100 // any vim visual mode (including line mode)
101 if (cursor_shape == CursorShape::Block || cursor_shape == CursorShape::Hollow)
102 && !range.is_empty()
103 && !selection.reversed
104 {
105 if head.column() > 0 {
106 head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left)
107 } else if head.row().0 > 0 && head != map.max_point() {
108 head = map.clip_point(
109 DisplayPoint::new(
110 head.row().previous_row(),
111 map.line_len(head.row().previous_row()),
112 ),
113 Bias::Left,
114 );
115 // updating range.end is a no-op unless you're cursor is
116 // on the newline containing a multi-buffer divider
117 // in which case the clip_point may have moved the head up
118 // an additional row.
119 range.end = DisplayPoint::new(head.row().next_row(), 0);
120 active_rows.end = head.row();
121 }
122 }
123
124 Self {
125 head,
126 cursor_shape,
127 is_newest,
128 is_local,
129 range,
130 active_rows,
131 user_name,
132 }
133 }
134}
135
136pub struct EditorElement {
137 editor: View<Editor>,
138 style: EditorStyle,
139}
140
141type DisplayRowDelta = u32;
142
143impl EditorElement {
144 pub(crate) const SCROLLBAR_WIDTH: Pixels = px(13.);
145
146 pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {
147 Self {
148 editor: editor.clone(),
149 style,
150 }
151 }
152
153 fn register_actions(&self, cx: &mut WindowContext) {
154 let view = &self.editor;
155 view.update(cx, |editor, cx| {
156 for action in editor.editor_actions.iter() {
157 (action)(cx)
158 }
159 });
160
161 crate::rust_analyzer_ext::apply_related_actions(view, cx);
162 register_action(view, cx, Editor::move_left);
163 register_action(view, cx, Editor::move_right);
164 register_action(view, cx, Editor::move_down);
165 register_action(view, cx, Editor::move_down_by_lines);
166 register_action(view, cx, Editor::select_down_by_lines);
167 register_action(view, cx, Editor::move_up);
168 register_action(view, cx, Editor::move_up_by_lines);
169 register_action(view, cx, Editor::select_up_by_lines);
170 register_action(view, cx, Editor::cancel);
171 register_action(view, cx, Editor::newline);
172 register_action(view, cx, Editor::newline_above);
173 register_action(view, cx, Editor::newline_below);
174 register_action(view, cx, Editor::backspace);
175 register_action(view, cx, Editor::delete);
176 register_action(view, cx, Editor::tab);
177 register_action(view, cx, Editor::tab_prev);
178 register_action(view, cx, Editor::indent);
179 register_action(view, cx, Editor::outdent);
180 register_action(view, cx, Editor::delete_line);
181 register_action(view, cx, Editor::join_lines);
182 register_action(view, cx, Editor::sort_lines_case_sensitive);
183 register_action(view, cx, Editor::sort_lines_case_insensitive);
184 register_action(view, cx, Editor::reverse_lines);
185 register_action(view, cx, Editor::shuffle_lines);
186 register_action(view, cx, Editor::convert_to_upper_case);
187 register_action(view, cx, Editor::convert_to_lower_case);
188 register_action(view, cx, Editor::convert_to_title_case);
189 register_action(view, cx, Editor::convert_to_snake_case);
190 register_action(view, cx, Editor::convert_to_kebab_case);
191 register_action(view, cx, Editor::convert_to_upper_camel_case);
192 register_action(view, cx, Editor::convert_to_lower_camel_case);
193 register_action(view, cx, Editor::convert_to_opposite_case);
194 register_action(view, cx, Editor::delete_to_previous_word_start);
195 register_action(view, cx, Editor::delete_to_previous_subword_start);
196 register_action(view, cx, Editor::delete_to_next_word_end);
197 register_action(view, cx, Editor::delete_to_next_subword_end);
198 register_action(view, cx, Editor::delete_to_beginning_of_line);
199 register_action(view, cx, Editor::delete_to_end_of_line);
200 register_action(view, cx, Editor::cut_to_end_of_line);
201 register_action(view, cx, Editor::duplicate_line_up);
202 register_action(view, cx, Editor::duplicate_line_down);
203 register_action(view, cx, Editor::move_line_up);
204 register_action(view, cx, Editor::move_line_down);
205 register_action(view, cx, Editor::transpose);
206 register_action(view, cx, Editor::cut);
207 register_action(view, cx, Editor::copy);
208 register_action(view, cx, Editor::paste);
209 register_action(view, cx, Editor::undo);
210 register_action(view, cx, Editor::redo);
211 register_action(view, cx, Editor::move_page_up);
212 register_action(view, cx, Editor::move_page_down);
213 register_action(view, cx, Editor::next_screen);
214 register_action(view, cx, Editor::scroll_cursor_top);
215 register_action(view, cx, Editor::scroll_cursor_center);
216 register_action(view, cx, Editor::scroll_cursor_bottom);
217 register_action(view, cx, |editor, _: &LineDown, cx| {
218 editor.scroll_screen(&ScrollAmount::Line(1.), cx)
219 });
220 register_action(view, cx, |editor, _: &LineUp, cx| {
221 editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
222 });
223 register_action(view, cx, |editor, _: &HalfPageDown, cx| {
224 editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
225 });
226 register_action(view, cx, |editor, _: &HalfPageUp, cx| {
227 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
228 });
229 register_action(view, cx, |editor, _: &PageDown, cx| {
230 editor.scroll_screen(&ScrollAmount::Page(1.), cx)
231 });
232 register_action(view, cx, |editor, _: &PageUp, cx| {
233 editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
234 });
235 register_action(view, cx, Editor::move_to_previous_word_start);
236 register_action(view, cx, Editor::move_to_previous_subword_start);
237 register_action(view, cx, Editor::move_to_next_word_end);
238 register_action(view, cx, Editor::move_to_next_subword_end);
239 register_action(view, cx, Editor::move_to_beginning_of_line);
240 register_action(view, cx, Editor::move_to_end_of_line);
241 register_action(view, cx, Editor::move_to_start_of_paragraph);
242 register_action(view, cx, Editor::move_to_end_of_paragraph);
243 register_action(view, cx, Editor::move_to_beginning);
244 register_action(view, cx, Editor::move_to_end);
245 register_action(view, cx, Editor::select_up);
246 register_action(view, cx, Editor::select_down);
247 register_action(view, cx, Editor::select_left);
248 register_action(view, cx, Editor::select_right);
249 register_action(view, cx, Editor::select_to_previous_word_start);
250 register_action(view, cx, Editor::select_to_previous_subword_start);
251 register_action(view, cx, Editor::select_to_next_word_end);
252 register_action(view, cx, Editor::select_to_next_subword_end);
253 register_action(view, cx, Editor::select_to_beginning_of_line);
254 register_action(view, cx, Editor::select_to_end_of_line);
255 register_action(view, cx, Editor::select_to_start_of_paragraph);
256 register_action(view, cx, Editor::select_to_end_of_paragraph);
257 register_action(view, cx, Editor::select_to_beginning);
258 register_action(view, cx, Editor::select_to_end);
259 register_action(view, cx, Editor::select_all);
260 register_action(view, cx, |editor, action, cx| {
261 editor.select_all_matches(action, cx).log_err();
262 });
263 register_action(view, cx, Editor::select_line);
264 register_action(view, cx, Editor::split_selection_into_lines);
265 register_action(view, cx, Editor::add_selection_above);
266 register_action(view, cx, Editor::add_selection_below);
267 register_action(view, cx, |editor, action, cx| {
268 editor.select_next(action, cx).log_err();
269 });
270 register_action(view, cx, |editor, action, cx| {
271 editor.select_previous(action, cx).log_err();
272 });
273 register_action(view, cx, Editor::toggle_comments);
274 register_action(view, cx, Editor::select_larger_syntax_node);
275 register_action(view, cx, Editor::select_smaller_syntax_node);
276 register_action(view, cx, Editor::move_to_enclosing_bracket);
277 register_action(view, cx, Editor::undo_selection);
278 register_action(view, cx, Editor::redo_selection);
279 if !view.read(cx).is_singleton(cx) {
280 register_action(view, cx, Editor::expand_excerpts);
281 }
282 register_action(view, cx, Editor::go_to_diagnostic);
283 register_action(view, cx, Editor::go_to_prev_diagnostic);
284 register_action(view, cx, Editor::go_to_hunk);
285 register_action(view, cx, Editor::go_to_prev_hunk);
286 register_action(view, cx, |editor, a, cx| {
287 editor.go_to_definition(a, cx).detach_and_log_err(cx);
288 });
289 register_action(view, cx, |editor, a, cx| {
290 editor.go_to_definition_split(a, cx).detach_and_log_err(cx);
291 });
292 register_action(view, cx, |editor, a, cx| {
293 editor.go_to_implementation(a, cx).detach_and_log_err(cx);
294 });
295 register_action(view, cx, |editor, a, cx| {
296 editor
297 .go_to_implementation_split(a, cx)
298 .detach_and_log_err(cx);
299 });
300 register_action(view, cx, |editor, a, cx| {
301 editor.go_to_type_definition(a, cx).detach_and_log_err(cx);
302 });
303 register_action(view, cx, |editor, a, cx| {
304 editor
305 .go_to_type_definition_split(a, cx)
306 .detach_and_log_err(cx);
307 });
308 register_action(view, cx, Editor::open_url);
309 register_action(view, cx, Editor::fold);
310 register_action(view, cx, Editor::fold_at);
311 register_action(view, cx, Editor::unfold_lines);
312 register_action(view, cx, Editor::unfold_at);
313 register_action(view, cx, Editor::fold_selected_ranges);
314 register_action(view, cx, Editor::show_completions);
315 register_action(view, cx, Editor::toggle_code_actions);
316 register_action(view, cx, Editor::open_excerpts);
317 register_action(view, cx, Editor::open_excerpts_in_split);
318 register_action(view, cx, Editor::toggle_soft_wrap);
319 register_action(view, cx, Editor::toggle_line_numbers);
320 register_action(view, cx, Editor::toggle_inlay_hints);
321 register_action(view, cx, hover_popover::hover);
322 register_action(view, cx, Editor::reveal_in_finder);
323 register_action(view, cx, Editor::copy_path);
324 register_action(view, cx, Editor::copy_relative_path);
325 register_action(view, cx, Editor::copy_highlight_json);
326 register_action(view, cx, Editor::copy_permalink_to_line);
327 register_action(view, cx, Editor::open_permalink_to_line);
328 register_action(view, cx, Editor::toggle_git_blame);
329 register_action(view, cx, Editor::toggle_git_blame_inline);
330 register_action(view, cx, Editor::toggle_hunk_diff);
331 register_action(view, cx, Editor::expand_all_hunk_diffs);
332 register_action(view, cx, |editor, action, cx| {
333 if let Some(task) = editor.format(action, cx) {
334 task.detach_and_log_err(cx);
335 } else {
336 cx.propagate();
337 }
338 });
339 register_action(view, cx, Editor::restart_language_server);
340 register_action(view, cx, Editor::show_character_palette);
341 register_action(view, cx, |editor, action, cx| {
342 if let Some(task) = editor.confirm_completion(action, cx) {
343 task.detach_and_log_err(cx);
344 } else {
345 cx.propagate();
346 }
347 });
348 register_action(view, cx, |editor, action, cx| {
349 if let Some(task) = editor.confirm_code_action(action, cx) {
350 task.detach_and_log_err(cx);
351 } else {
352 cx.propagate();
353 }
354 });
355 register_action(view, cx, |editor, action, cx| {
356 if let Some(task) = editor.rename(action, cx) {
357 task.detach_and_log_err(cx);
358 } else {
359 cx.propagate();
360 }
361 });
362 register_action(view, cx, |editor, action, cx| {
363 if let Some(task) = editor.confirm_rename(action, cx) {
364 task.detach_and_log_err(cx);
365 } else {
366 cx.propagate();
367 }
368 });
369 register_action(view, cx, |editor, action, cx| {
370 if let Some(task) = editor.find_all_references(action, cx) {
371 task.detach_and_log_err(cx);
372 } else {
373 cx.propagate();
374 }
375 });
376 register_action(view, cx, Editor::next_inline_completion);
377 register_action(view, cx, Editor::previous_inline_completion);
378 register_action(view, cx, Editor::show_inline_completion);
379 register_action(view, cx, Editor::context_menu_first);
380 register_action(view, cx, Editor::context_menu_prev);
381 register_action(view, cx, Editor::context_menu_next);
382 register_action(view, cx, Editor::context_menu_last);
383 register_action(view, cx, Editor::display_cursor_names);
384 register_action(view, cx, Editor::unique_lines_case_insensitive);
385 register_action(view, cx, Editor::unique_lines_case_sensitive);
386 register_action(view, cx, Editor::accept_partial_inline_completion);
387 register_action(view, cx, Editor::revert_selected_hunks);
388 register_action(view, cx, Editor::open_active_item_in_terminal)
389 }
390
391 fn register_key_listeners(&self, cx: &mut WindowContext, layout: &EditorLayout) {
392 let position_map = layout.position_map.clone();
393 cx.on_key_event({
394 let editor = self.editor.clone();
395 let text_hitbox = layout.text_hitbox.clone();
396 move |event: &ModifiersChangedEvent, phase, cx| {
397 if phase != DispatchPhase::Bubble {
398 return;
399 }
400
401 editor.update(cx, |editor, cx| {
402 Self::modifiers_changed(editor, event, &position_map, &text_hitbox, cx)
403 })
404 }
405 });
406 }
407
408 fn modifiers_changed(
409 editor: &mut Editor,
410 event: &ModifiersChangedEvent,
411 position_map: &PositionMap,
412 text_hitbox: &Hitbox,
413 cx: &mut ViewContext<Editor>,
414 ) {
415 let mouse_position = cx.mouse_position();
416 if !text_hitbox.is_hovered(cx) {
417 return;
418 }
419
420 editor.update_hovered_link(
421 position_map.point_for_position(text_hitbox.bounds, mouse_position),
422 &position_map.snapshot,
423 event.modifiers,
424 cx,
425 )
426 }
427
428 fn mouse_left_down(
429 editor: &mut Editor,
430 event: &MouseDownEvent,
431 hovered_hunk: Option<&HunkToExpand>,
432 position_map: &PositionMap,
433 text_hitbox: &Hitbox,
434 gutter_hitbox: &Hitbox,
435 cx: &mut ViewContext<Editor>,
436 ) {
437 if cx.default_prevented() {
438 return;
439 }
440
441 let mut click_count = event.click_count;
442 let mut modifiers = event.modifiers;
443
444 if let Some(hovered_hunk) = hovered_hunk {
445 editor.expand_diff_hunk(None, hovered_hunk, cx);
446 cx.notify();
447 return;
448 } else if gutter_hitbox.is_hovered(cx) {
449 click_count = 3; // Simulate triple-click when clicking the gutter to select lines
450 } else if !text_hitbox.is_hovered(cx) {
451 return;
452 }
453
454 if click_count == 2 && !editor.buffer().read(cx).is_singleton() {
455 match EditorSettings::get_global(cx).double_click_in_multibuffer {
456 DoubleClickInMultibuffer::Select => {
457 // do nothing special on double click, all selection logic is below
458 }
459 DoubleClickInMultibuffer::Open => {
460 if modifiers.alt {
461 // if double click is made with alt, pretend it's a regular double click without opening and alt,
462 // and run the selection logic.
463 modifiers.alt = false;
464 } else {
465 // if double click is made without alt, open the corresponding excerp
466 editor.open_excerpts(&OpenExcerpts, cx);
467 return;
468 }
469 }
470 }
471 }
472
473 let point_for_position =
474 position_map.point_for_position(text_hitbox.bounds, event.position);
475 let position = point_for_position.previous_valid;
476 if modifiers.shift && modifiers.alt {
477 editor.select(
478 SelectPhase::BeginColumnar {
479 position,
480 reset: false,
481 goal_column: point_for_position.exact_unclipped.column(),
482 },
483 cx,
484 );
485 } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.secondary()
486 {
487 editor.select(
488 SelectPhase::Extend {
489 position,
490 click_count,
491 },
492 cx,
493 );
494 } else {
495 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
496 let multi_cursor_modifier = match multi_cursor_setting {
497 MultiCursorModifier::Alt => modifiers.alt,
498 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
499 };
500 editor.select(
501 SelectPhase::Begin {
502 position,
503 add: multi_cursor_modifier,
504 click_count,
505 },
506 cx,
507 );
508 }
509
510 cx.stop_propagation();
511 }
512
513 fn mouse_right_down(
514 editor: &mut Editor,
515 event: &MouseDownEvent,
516 position_map: &PositionMap,
517 text_hitbox: &Hitbox,
518 cx: &mut ViewContext<Editor>,
519 ) {
520 if !text_hitbox.is_hovered(cx) {
521 return;
522 }
523 let point_for_position =
524 position_map.point_for_position(text_hitbox.bounds, event.position);
525 mouse_context_menu::deploy_context_menu(
526 editor,
527 event.position,
528 point_for_position.previous_valid,
529 cx,
530 );
531 cx.stop_propagation();
532 }
533
534 fn mouse_middle_down(
535 editor: &mut Editor,
536 event: &MouseDownEvent,
537 position_map: &PositionMap,
538 text_hitbox: &Hitbox,
539 cx: &mut ViewContext<Editor>,
540 ) {
541 if cx.default_prevented() {
542 return;
543 }
544
545 let point_for_position =
546 position_map.point_for_position(text_hitbox.bounds, event.position);
547 let position = point_for_position.previous_valid;
548
549 editor.select(
550 SelectPhase::BeginColumnar {
551 position,
552 reset: true,
553 goal_column: point_for_position.exact_unclipped.column(),
554 },
555 cx,
556 );
557 }
558
559 fn mouse_up(
560 editor: &mut Editor,
561 event: &MouseUpEvent,
562 position_map: &PositionMap,
563 text_hitbox: &Hitbox,
564 cx: &mut ViewContext<Editor>,
565 ) {
566 let end_selection = editor.has_pending_selection();
567 let pending_nonempty_selections = editor.has_pending_nonempty_selection();
568
569 if end_selection {
570 editor.select(SelectPhase::End, cx);
571 }
572
573 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
574 let multi_cursor_modifier = match multi_cursor_setting {
575 MultiCursorModifier::Alt => event.modifiers.secondary(),
576 MultiCursorModifier::CmdOrCtrl => event.modifiers.alt,
577 };
578
579 if !pending_nonempty_selections && multi_cursor_modifier && text_hitbox.is_hovered(cx) {
580 let point = position_map.point_for_position(text_hitbox.bounds, event.position);
581 editor.handle_click_hovered_link(point, event.modifiers, cx);
582
583 cx.stop_propagation();
584 } else if end_selection {
585 cx.stop_propagation();
586 } else if cfg!(target_os = "linux") && event.button == MouseButton::Middle {
587 if !text_hitbox.is_hovered(cx) || editor.read_only(cx) {
588 return;
589 }
590
591 #[cfg(target_os = "linux")]
592 if let Some(item) = cx.read_from_clipboard() {
593 let point_for_position =
594 position_map.point_for_position(text_hitbox.bounds, event.position);
595 let position = point_for_position.previous_valid;
596
597 editor.select(
598 SelectPhase::Begin {
599 position,
600 add: false,
601 click_count: 1,
602 },
603 cx,
604 );
605 editor.insert(item.text(), cx);
606 }
607 cx.stop_propagation()
608 }
609 }
610
611 fn mouse_dragged(
612 editor: &mut Editor,
613 event: &MouseMoveEvent,
614 position_map: &PositionMap,
615 text_bounds: Bounds<Pixels>,
616 cx: &mut ViewContext<Editor>,
617 ) {
618 if !editor.has_pending_selection() {
619 return;
620 }
621
622 let point_for_position = position_map.point_for_position(text_bounds, event.position);
623 let mut scroll_delta = gpui::Point::<f32>::default();
624 let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
625 let top = text_bounds.origin.y + vertical_margin;
626 let bottom = text_bounds.lower_left().y - vertical_margin;
627 if event.position.y < top {
628 scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y);
629 }
630 if event.position.y > bottom {
631 scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
632 }
633
634 let horizontal_margin = position_map.line_height.min(text_bounds.size.width / 3.0);
635 let left = text_bounds.origin.x + horizontal_margin;
636 let right = text_bounds.upper_right().x - horizontal_margin;
637 if event.position.x < left {
638 scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
639 }
640 if event.position.x > right {
641 scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
642 }
643
644 editor.select(
645 SelectPhase::Update {
646 position: point_for_position.previous_valid,
647 goal_column: point_for_position.exact_unclipped.column(),
648 scroll_delta,
649 },
650 cx,
651 );
652 }
653
654 fn mouse_moved(
655 editor: &mut Editor,
656 event: &MouseMoveEvent,
657 position_map: &PositionMap,
658 text_hitbox: &Hitbox,
659 gutter_hitbox: &Hitbox,
660 cx: &mut ViewContext<Editor>,
661 ) {
662 let modifiers = event.modifiers;
663 let gutter_hovered = gutter_hitbox.is_hovered(cx);
664 editor.set_gutter_hovered(gutter_hovered, cx);
665
666 // Don't trigger hover popover if mouse is hovering over context menu
667 if text_hitbox.is_hovered(cx) {
668 let point_for_position =
669 position_map.point_for_position(text_hitbox.bounds, event.position);
670
671 editor.update_hovered_link(point_for_position, &position_map.snapshot, modifiers, cx);
672
673 if let Some(point) = point_for_position.as_valid() {
674 let anchor = position_map
675 .snapshot
676 .buffer_snapshot
677 .anchor_before(point.to_offset(&position_map.snapshot, Bias::Left));
678 hover_at(editor, Some(anchor), cx);
679 Self::update_visible_cursor(editor, point, position_map, cx);
680 } else {
681 hover_at(editor, None, cx);
682 }
683 } else {
684 editor.hide_hovered_link(cx);
685 hover_at(editor, None, cx);
686 if gutter_hovered {
687 cx.stop_propagation();
688 }
689 }
690 }
691
692 fn update_visible_cursor(
693 editor: &mut Editor,
694 point: DisplayPoint,
695 position_map: &PositionMap,
696 cx: &mut ViewContext<Editor>,
697 ) {
698 let snapshot = &position_map.snapshot;
699 let Some(hub) = editor.collaboration_hub() else {
700 return;
701 };
702 let range = DisplayPoint::new(point.row(), point.column().saturating_sub(1))
703 ..DisplayPoint::new(
704 point.row(),
705 (point.column() + 1).min(snapshot.line_len(point.row())),
706 );
707
708 let range = snapshot
709 .buffer_snapshot
710 .anchor_at(range.start.to_point(&snapshot.display_snapshot), Bias::Left)
711 ..snapshot
712 .buffer_snapshot
713 .anchor_at(range.end.to_point(&snapshot.display_snapshot), Bias::Right);
714
715 let Some(selection) = snapshot.remote_selections_in_range(&range, hub, cx).next() else {
716 return;
717 };
718 let key = crate::HoveredCursor {
719 replica_id: selection.replica_id,
720 selection_id: selection.selection.id,
721 };
722 editor.hovered_cursors.insert(
723 key.clone(),
724 cx.spawn(|editor, mut cx| async move {
725 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
726 editor
727 .update(&mut cx, |editor, cx| {
728 editor.hovered_cursors.remove(&key);
729 cx.notify();
730 })
731 .ok();
732 }),
733 );
734 cx.notify()
735 }
736
737 fn layout_selections(
738 &self,
739 start_anchor: Anchor,
740 end_anchor: Anchor,
741 snapshot: &EditorSnapshot,
742 start_row: DisplayRow,
743 end_row: DisplayRow,
744 cx: &mut WindowContext,
745 ) -> (
746 Vec<(PlayerColor, Vec<SelectionLayout>)>,
747 BTreeMap<DisplayRow, bool>,
748 Option<DisplayPoint>,
749 ) {
750 let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
751 let mut active_rows = BTreeMap::new();
752 let mut newest_selection_head = None;
753 let editor = self.editor.read(cx);
754
755 if editor.show_local_selections {
756 let mut local_selections: Vec<Selection<Point>> = editor
757 .selections
758 .disjoint_in_range(start_anchor..end_anchor, cx);
759 local_selections.extend(editor.selections.pending(cx));
760 let mut layouts = Vec::new();
761 let newest = editor.selections.newest(cx);
762 for selection in local_selections.drain(..) {
763 let is_empty = selection.start == selection.end;
764 let is_newest = selection == newest;
765
766 let layout = SelectionLayout::new(
767 selection,
768 editor.selections.line_mode,
769 editor.cursor_shape,
770 &snapshot.display_snapshot,
771 is_newest,
772 editor.leader_peer_id.is_none(),
773 None,
774 );
775 if is_newest {
776 newest_selection_head = Some(layout.head);
777 }
778
779 for row in cmp::max(layout.active_rows.start.0, start_row.0)
780 ..=cmp::min(layout.active_rows.end.0, end_row.0)
781 {
782 let contains_non_empty_selection =
783 active_rows.entry(DisplayRow(row)).or_insert(!is_empty);
784 *contains_non_empty_selection |= !is_empty;
785 }
786 layouts.push(layout);
787 }
788
789 let player = if editor.read_only(cx) {
790 cx.theme().players().read_only()
791 } else {
792 self.style.local_player
793 };
794
795 selections.push((player, layouts));
796 }
797
798 if let Some(collaboration_hub) = &editor.collaboration_hub {
799 // When following someone, render the local selections in their color.
800 if let Some(leader_id) = editor.leader_peer_id {
801 if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
802 if let Some(participant_index) = collaboration_hub
803 .user_participant_indices(cx)
804 .get(&collaborator.user_id)
805 {
806 if let Some((local_selection_style, _)) = selections.first_mut() {
807 *local_selection_style = cx
808 .theme()
809 .players()
810 .color_for_participant(participant_index.0);
811 }
812 }
813 }
814 }
815
816 let mut remote_selections = HashMap::default();
817 for selection in snapshot.remote_selections_in_range(
818 &(start_anchor..end_anchor),
819 collaboration_hub.as_ref(),
820 cx,
821 ) {
822 let selection_style = Self::get_participant_color(selection.participant_index, cx);
823
824 // Don't re-render the leader's selections, since the local selections
825 // match theirs.
826 if Some(selection.peer_id) == editor.leader_peer_id {
827 continue;
828 }
829 let key = HoveredCursor {
830 replica_id: selection.replica_id,
831 selection_id: selection.selection.id,
832 };
833
834 let is_shown =
835 editor.show_cursor_names || editor.hovered_cursors.contains_key(&key);
836
837 remote_selections
838 .entry(selection.replica_id)
839 .or_insert((selection_style, Vec::new()))
840 .1
841 .push(SelectionLayout::new(
842 selection.selection,
843 selection.line_mode,
844 selection.cursor_shape,
845 &snapshot.display_snapshot,
846 false,
847 false,
848 if is_shown { selection.user_name } else { None },
849 ));
850 }
851
852 selections.extend(remote_selections.into_values());
853 }
854 (selections, active_rows, newest_selection_head)
855 }
856
857 #[allow(clippy::too_many_arguments)]
858 fn layout_folds(
859 &self,
860 snapshot: &EditorSnapshot,
861 content_origin: gpui::Point<Pixels>,
862 visible_anchor_range: Range<Anchor>,
863 visible_display_row_range: Range<DisplayRow>,
864 scroll_pixel_position: gpui::Point<Pixels>,
865 line_height: Pixels,
866 line_layouts: &[LineWithInvisibles],
867 cx: &mut WindowContext,
868 ) -> Vec<FoldLayout> {
869 snapshot
870 .folds_in_range(visible_anchor_range.clone())
871 .filter_map(|fold| {
872 let fold_range = fold.range.clone();
873 let display_range = fold.range.start.to_display_point(&snapshot)
874 ..fold.range.end.to_display_point(&snapshot);
875 debug_assert_eq!(display_range.start.row(), display_range.end.row());
876 let row = display_range.start.row();
877 debug_assert!(row < visible_display_row_range.end);
878 let line_layout = line_layouts
879 .get(row.minus(visible_display_row_range.start) as usize)
880 .map(|l| &l.line)?;
881
882 let start_x = content_origin.x
883 + line_layout.x_for_index(display_range.start.column() as usize)
884 - scroll_pixel_position.x;
885 let start_y =
886 content_origin.y + row.as_f32() * line_height - scroll_pixel_position.y;
887 let end_x = content_origin.x
888 + line_layout.x_for_index(display_range.end.column() as usize)
889 - scroll_pixel_position.x;
890
891 let fold_bounds = Bounds {
892 origin: point(start_x, start_y),
893 size: size(end_x - start_x, line_height),
894 };
895
896 let mut hover_element = div()
897 .id(fold.id)
898 .size_full()
899 .cursor_pointer()
900 .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
901 .on_click(
902 cx.listener_for(&self.editor, move |editor: &mut Editor, _, cx| {
903 editor.unfold_ranges(
904 [fold_range.start..fold_range.end],
905 true,
906 false,
907 cx,
908 );
909 cx.stop_propagation();
910 }),
911 )
912 .into_any();
913 hover_element.prepaint_as_root(fold_bounds.origin, fold_bounds.size.into(), cx);
914 Some(FoldLayout {
915 display_range,
916 hover_element,
917 })
918 })
919 .collect()
920 }
921
922 fn collect_cursors(
923 &self,
924 snapshot: &EditorSnapshot,
925 cx: &mut WindowContext,
926 ) -> Vec<(DisplayPoint, Hsla)> {
927 let editor = self.editor.read(cx);
928 let mut cursors = Vec::new();
929 let mut skip_local = false;
930 let mut add_cursor = |anchor: Anchor, color| {
931 cursors.push((anchor.to_display_point(&snapshot.display_snapshot), color));
932 };
933 // Remote cursors
934 if let Some(collaboration_hub) = &editor.collaboration_hub {
935 for remote_selection in snapshot.remote_selections_in_range(
936 &(Anchor::min()..Anchor::max()),
937 collaboration_hub.deref(),
938 cx,
939 ) {
940 let color = Self::get_participant_color(remote_selection.participant_index, cx);
941 add_cursor(remote_selection.selection.head(), color.cursor);
942 if Some(remote_selection.peer_id) == editor.leader_peer_id {
943 skip_local = true;
944 }
945 }
946 }
947 // Local cursors
948 if !skip_local {
949 let color = cx.theme().players().local().cursor;
950 editor.selections.disjoint.iter().for_each(|selection| {
951 add_cursor(selection.head(), color);
952 });
953 if let Some(ref selection) = editor.selections.pending_anchor() {
954 add_cursor(selection.head(), color);
955 }
956 }
957 cursors
958 }
959
960 #[allow(clippy::too_many_arguments)]
961 fn layout_visible_cursors(
962 &self,
963 snapshot: &EditorSnapshot,
964 selections: &[(PlayerColor, Vec<SelectionLayout>)],
965 visible_display_row_range: Range<DisplayRow>,
966 line_layouts: &[LineWithInvisibles],
967 text_hitbox: &Hitbox,
968 content_origin: gpui::Point<Pixels>,
969 scroll_position: gpui::Point<f32>,
970 scroll_pixel_position: gpui::Point<Pixels>,
971 line_height: Pixels,
972 em_width: Pixels,
973 autoscroll_containing_element: bool,
974 cx: &mut WindowContext,
975 ) -> Vec<CursorLayout> {
976 let mut autoscroll_bounds = None;
977 let cursor_layouts = self.editor.update(cx, |editor, cx| {
978 let mut cursors = Vec::new();
979 for (player_color, selections) in selections {
980 for selection in selections {
981 let cursor_position = selection.head;
982
983 let in_range = visible_display_row_range.contains(&cursor_position.row());
984 if (selection.is_local && !editor.show_local_cursors(cx)) || !in_range {
985 continue;
986 }
987
988 let cursor_row_layout = &line_layouts
989 [cursor_position.row().minus(visible_display_row_range.start) as usize]
990 .line;
991 let cursor_column = cursor_position.column() as usize;
992
993 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
994 let mut block_width =
995 cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
996 if block_width == Pixels::ZERO {
997 block_width = em_width;
998 }
999 let block_text = if let CursorShape::Block = selection.cursor_shape {
1000 snapshot.display_chars_at(cursor_position).next().and_then(
1001 |(character, _)| {
1002 let text = if character == '\n' {
1003 SharedString::from(" ")
1004 } else {
1005 SharedString::from(character.to_string())
1006 };
1007 let len = text.len();
1008
1009 let font = cursor_row_layout
1010 .font_id_for_index(cursor_column)
1011 .and_then(|cursor_font_id| {
1012 cx.text_system().get_font_for_id(cursor_font_id)
1013 })
1014 .unwrap_or(self.style.text.font());
1015
1016 cx.text_system()
1017 .shape_line(
1018 text,
1019 cursor_row_layout.font_size,
1020 &[TextRun {
1021 len,
1022 font,
1023 color: self.style.background,
1024 background_color: None,
1025 strikethrough: None,
1026 underline: None,
1027 }],
1028 )
1029 .log_err()
1030 },
1031 )
1032 } else {
1033 None
1034 };
1035
1036 let x = cursor_character_x - scroll_pixel_position.x;
1037 let y = (cursor_position.row().as_f32()
1038 - scroll_pixel_position.y / line_height)
1039 * line_height;
1040 if selection.is_newest {
1041 editor.pixel_position_of_newest_cursor = Some(point(
1042 text_hitbox.origin.x + x + block_width / 2.,
1043 text_hitbox.origin.y + y + line_height / 2.,
1044 ));
1045
1046 if autoscroll_containing_element {
1047 let top = text_hitbox.origin.y
1048 + (cursor_position.row().as_f32() - scroll_position.y - 3.).max(0.)
1049 * line_height;
1050 let left = text_hitbox.origin.x
1051 + (cursor_position.column() as f32 - scroll_position.x - 3.)
1052 .max(0.)
1053 * em_width;
1054
1055 let bottom = text_hitbox.origin.y
1056 + (cursor_position.row().as_f32() - scroll_position.y + 4.)
1057 * line_height;
1058 let right = text_hitbox.origin.x
1059 + (cursor_position.column() as f32 - scroll_position.x + 4.)
1060 * em_width;
1061
1062 autoscroll_bounds =
1063 Some(Bounds::from_corners(point(left, top), point(right, bottom)))
1064 }
1065 }
1066
1067 let mut cursor = CursorLayout {
1068 color: player_color.cursor,
1069 block_width,
1070 origin: point(x, y),
1071 line_height,
1072 shape: selection.cursor_shape,
1073 block_text,
1074 cursor_name: None,
1075 };
1076 let cursor_name = selection.user_name.clone().map(|name| CursorName {
1077 string: name,
1078 color: self.style.background,
1079 is_top_row: cursor_position.row().0 == 0,
1080 });
1081 cursor.layout(content_origin, cursor_name, cx);
1082 cursors.push(cursor);
1083 }
1084 }
1085 cursors
1086 });
1087
1088 if let Some(bounds) = autoscroll_bounds {
1089 cx.request_autoscroll(bounds);
1090 }
1091
1092 cursor_layouts
1093 }
1094
1095 fn layout_scrollbar(
1096 &self,
1097 snapshot: &EditorSnapshot,
1098 bounds: Bounds<Pixels>,
1099 scroll_position: gpui::Point<f32>,
1100 rows_per_page: f32,
1101 non_visible_cursors: bool,
1102 cx: &mut WindowContext,
1103 ) -> Option<ScrollbarLayout> {
1104 let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
1105 let show_scrollbars = match scrollbar_settings.show {
1106 ShowScrollbar::Auto => {
1107 let editor = self.editor.read(cx);
1108 let is_singleton = editor.is_singleton(cx);
1109 // Git
1110 (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
1111 ||
1112 // Buffer Search Results
1113 (is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
1114 ||
1115 // Selected Symbol Occurrences
1116 (is_singleton && scrollbar_settings.selected_symbol && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
1117 ||
1118 // Diagnostics
1119 (is_singleton && scrollbar_settings.diagnostics && snapshot.buffer_snapshot.has_diagnostics())
1120 ||
1121 // Cursors out of sight
1122 non_visible_cursors
1123 ||
1124 // Scrollmanager
1125 editor.scroll_manager.scrollbars_visible()
1126 }
1127 ShowScrollbar::System => self.editor.read(cx).scroll_manager.scrollbars_visible(),
1128 ShowScrollbar::Always => true,
1129 ShowScrollbar::Never => false,
1130 };
1131 if snapshot.mode != EditorMode::Full {
1132 return None;
1133 }
1134
1135 let visible_row_range = scroll_position.y..scroll_position.y + rows_per_page;
1136
1137 // If a drag took place after we started dragging the scrollbar,
1138 // cancel the scrollbar drag.
1139 if cx.has_active_drag() {
1140 self.editor.update(cx, |editor, cx| {
1141 editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
1142 });
1143 }
1144
1145 let track_bounds = Bounds::from_corners(
1146 point(self.scrollbar_left(&bounds), bounds.origin.y),
1147 point(bounds.lower_right().x, bounds.lower_left().y),
1148 );
1149
1150 let height = bounds.size.height;
1151 let total_rows = snapshot.max_point().row().as_f32() + rows_per_page;
1152 let px_per_row = height / total_rows;
1153 let thumb_height = (rows_per_page * px_per_row).max(ScrollbarLayout::MIN_THUMB_HEIGHT);
1154 let row_height = (height - thumb_height) / snapshot.max_point().row().as_f32();
1155
1156 Some(ScrollbarLayout {
1157 hitbox: cx.insert_hitbox(track_bounds, false),
1158 visible_row_range,
1159 row_height,
1160 visible: show_scrollbars,
1161 thumb_height,
1162 })
1163 }
1164
1165 #[allow(clippy::too_many_arguments)]
1166 fn layout_gutter_fold_indicators(
1167 &self,
1168 fold_statuses: Vec<Option<(FoldStatus, MultiBufferRow, bool)>>,
1169 line_height: Pixels,
1170 gutter_dimensions: &GutterDimensions,
1171 gutter_settings: crate::editor_settings::Gutter,
1172 scroll_pixel_position: gpui::Point<Pixels>,
1173 gutter_hitbox: &Hitbox,
1174 cx: &mut WindowContext,
1175 ) -> Vec<Option<AnyElement>> {
1176 let mut indicators = self.editor.update(cx, |editor, cx| {
1177 editor.render_fold_indicators(
1178 fold_statuses,
1179 &self.style,
1180 editor.gutter_hovered,
1181 line_height,
1182 gutter_dimensions.margin,
1183 cx,
1184 )
1185 });
1186
1187 for (ix, fold_indicator) in indicators.iter_mut().enumerate() {
1188 if let Some(fold_indicator) = fold_indicator {
1189 debug_assert!(gutter_settings.folds);
1190 let available_space = size(
1191 AvailableSpace::MinContent,
1192 AvailableSpace::Definite(line_height * 0.55),
1193 );
1194 let fold_indicator_size = fold_indicator.layout_as_root(available_space, cx);
1195
1196 let position = point(
1197 gutter_dimensions.width - gutter_dimensions.right_padding,
1198 ix as f32 * line_height - (scroll_pixel_position.y % line_height),
1199 );
1200 let centering_offset = point(
1201 (gutter_dimensions.right_padding + gutter_dimensions.margin
1202 - fold_indicator_size.width)
1203 / 2.,
1204 (line_height - fold_indicator_size.height) / 2.,
1205 );
1206 let origin = gutter_hitbox.origin + position + centering_offset;
1207 fold_indicator.prepaint_as_root(origin, available_space, cx);
1208 }
1209 }
1210
1211 indicators
1212 }
1213
1214 // Folds contained in a hunk are ignored apart from shrinking visual size
1215 // If a fold contains any hunks then that fold line is marked as modified
1216 fn layout_git_gutters(
1217 &self,
1218 line_height: Pixels,
1219 gutter_hitbox: &Hitbox,
1220 display_rows: Range<DisplayRow>,
1221 snapshot: &EditorSnapshot,
1222 cx: &mut WindowContext,
1223 ) -> Vec<(DisplayDiffHunk, Option<Hitbox>)> {
1224 let buffer_snapshot = &snapshot.buffer_snapshot;
1225
1226 let buffer_start_row = MultiBufferRow(
1227 DisplayPoint::new(display_rows.start, 0)
1228 .to_point(snapshot)
1229 .row,
1230 );
1231 let buffer_end_row = MultiBufferRow(
1232 DisplayPoint::new(display_rows.end, 0)
1233 .to_point(snapshot)
1234 .row,
1235 );
1236
1237 let expanded_hunk_display_rows = self.editor.update(cx, |editor, _| {
1238 editor
1239 .expanded_hunks
1240 .hunks(false)
1241 .map(|expanded_hunk| {
1242 let start_row = expanded_hunk
1243 .hunk_range
1244 .start
1245 .to_display_point(snapshot)
1246 .row();
1247 let end_row = expanded_hunk
1248 .hunk_range
1249 .end
1250 .to_display_point(snapshot)
1251 .row();
1252 (start_row, end_row)
1253 })
1254 .collect::<HashMap<_, _>>()
1255 });
1256
1257 buffer_snapshot
1258 .git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
1259 .map(|hunk| diff_hunk_to_display(&hunk, snapshot))
1260 .dedup()
1261 .map(|hunk| {
1262 let hitbox = if let DisplayDiffHunk::Unfolded {
1263 display_row_range, ..
1264 } = &hunk
1265 {
1266 let was_expanded = expanded_hunk_display_rows
1267 .get(&display_row_range.start)
1268 .map(|expanded_end_row| expanded_end_row == &display_row_range.end)
1269 .unwrap_or(false);
1270 if was_expanded {
1271 None
1272 } else {
1273 let hunk_bounds = Self::diff_hunk_bounds(
1274 &snapshot,
1275 line_height,
1276 gutter_hitbox.bounds,
1277 &hunk,
1278 );
1279 Some(cx.insert_hitbox(hunk_bounds, true))
1280 }
1281 } else {
1282 None
1283 };
1284 (hunk, hitbox)
1285 })
1286 .collect()
1287 }
1288
1289 #[allow(clippy::too_many_arguments)]
1290 fn layout_inline_blame(
1291 &self,
1292 display_row: DisplayRow,
1293 display_snapshot: &DisplaySnapshot,
1294 line_layout: &LineWithInvisibles,
1295 em_width: Pixels,
1296 content_origin: gpui::Point<Pixels>,
1297 scroll_pixel_position: gpui::Point<Pixels>,
1298 line_height: Pixels,
1299 cx: &mut WindowContext,
1300 ) -> Option<AnyElement> {
1301 if !self
1302 .editor
1303 .update(cx, |editor, cx| editor.render_git_blame_inline(cx))
1304 {
1305 return None;
1306 }
1307
1308 let workspace = self
1309 .editor
1310 .read(cx)
1311 .workspace
1312 .as_ref()
1313 .map(|(w, _)| w.clone());
1314
1315 let display_point = DisplayPoint::new(display_row, 0);
1316 let buffer_row = MultiBufferRow(display_point.to_point(display_snapshot).row);
1317
1318 let blame = self.editor.read(cx).blame.clone()?;
1319 let blame_entry = blame
1320 .update(cx, |blame, cx| {
1321 blame.blame_for_rows([Some(buffer_row)], cx).next()
1322 })
1323 .flatten()?;
1324
1325 let mut element =
1326 render_inline_blame_entry(&blame, blame_entry, &self.style, workspace, cx);
1327
1328 let start_y = content_origin.y
1329 + line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height);
1330
1331 let start_x = {
1332 const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
1333
1334 let padded_line_width =
1335 line_layout.line.width + (em_width * INLINE_BLAME_PADDING_EM_WIDTHS);
1336
1337 let min_column = ProjectSettings::get_global(cx)
1338 .git
1339 .inline_blame
1340 .and_then(|settings| settings.min_column)
1341 .map(|col| self.column_pixels(col as usize, cx))
1342 .unwrap_or(px(0.));
1343
1344 (content_origin.x - scroll_pixel_position.x) + max(padded_line_width, min_column)
1345 };
1346
1347 let absolute_offset = point(start_x, start_y);
1348 let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
1349
1350 element.prepaint_as_root(absolute_offset, available_space, cx);
1351
1352 Some(element)
1353 }
1354
1355 #[allow(clippy::too_many_arguments)]
1356 fn layout_blame_entries(
1357 &self,
1358 buffer_rows: impl Iterator<Item = Option<MultiBufferRow>>,
1359 em_width: Pixels,
1360 scroll_position: gpui::Point<f32>,
1361 line_height: Pixels,
1362 gutter_hitbox: &Hitbox,
1363 max_width: Option<Pixels>,
1364 cx: &mut WindowContext,
1365 ) -> Option<Vec<AnyElement>> {
1366 if !self
1367 .editor
1368 .update(cx, |editor, cx| editor.render_git_blame_gutter(cx))
1369 {
1370 return None;
1371 }
1372
1373 let blame = self.editor.read(cx).blame.clone()?;
1374 let blamed_rows: Vec<_> = blame.update(cx, |blame, cx| {
1375 blame.blame_for_rows(buffer_rows, cx).collect()
1376 });
1377
1378 let width = if let Some(max_width) = max_width {
1379 AvailableSpace::Definite(max_width)
1380 } else {
1381 AvailableSpace::MaxContent
1382 };
1383 let scroll_top = scroll_position.y * line_height;
1384 let start_x = em_width * 1;
1385
1386 let mut last_used_color: Option<(PlayerColor, Oid)> = None;
1387
1388 let shaped_lines = blamed_rows
1389 .into_iter()
1390 .enumerate()
1391 .flat_map(|(ix, blame_entry)| {
1392 if let Some(blame_entry) = blame_entry {
1393 let mut element = render_blame_entry(
1394 ix,
1395 &blame,
1396 blame_entry,
1397 &self.style,
1398 &mut last_used_color,
1399 self.editor.clone(),
1400 cx,
1401 );
1402
1403 let start_y = ix as f32 * line_height - (scroll_top % line_height);
1404 let absolute_offset = gutter_hitbox.origin + point(start_x, start_y);
1405
1406 element.prepaint_as_root(
1407 absolute_offset,
1408 size(width, AvailableSpace::MinContent),
1409 cx,
1410 );
1411
1412 Some(element)
1413 } else {
1414 None
1415 }
1416 })
1417 .collect();
1418
1419 Some(shaped_lines)
1420 }
1421
1422 fn layout_run_indicators(
1423 &self,
1424 line_height: Pixels,
1425 scroll_pixel_position: gpui::Point<Pixels>,
1426 gutter_dimensions: &GutterDimensions,
1427 gutter_hitbox: &Hitbox,
1428 snapshot: &EditorSnapshot,
1429 cx: &mut WindowContext,
1430 ) -> Vec<AnyElement> {
1431 self.editor.update(cx, |editor, cx| {
1432 let active_task_indicator_row =
1433 if let Some(crate::ContextMenu::CodeActions(CodeActionsMenu {
1434 deployed_from_indicator,
1435 actions,
1436 ..
1437 })) = editor.context_menu.read().as_ref()
1438 {
1439 actions
1440 .tasks
1441 .as_ref()
1442 .map(|tasks| tasks.position.to_display_point(snapshot).row())
1443 .or_else(|| *deployed_from_indicator)
1444 } else {
1445 None
1446 };
1447 editor
1448 .tasks
1449 .iter()
1450 .filter_map(|(_, (multibuffer_offset, _))| {
1451 let multibuffer_point = multibuffer_offset.to_point(&snapshot.buffer_snapshot);
1452 let multibuffer_row = MultiBufferRow(multibuffer_point.row);
1453 if snapshot.is_line_folded(multibuffer_row) {
1454 return None;
1455 }
1456 let display_row = multibuffer_point.to_display_point(snapshot).row();
1457 let button = editor.render_run_indicator(
1458 &self.style,
1459 Some(display_row) == active_task_indicator_row,
1460 display_row,
1461 cx,
1462 );
1463
1464 let button = prepaint_gutter_button(
1465 button,
1466 display_row,
1467 line_height,
1468 gutter_dimensions,
1469 scroll_pixel_position,
1470 gutter_hitbox,
1471 cx,
1472 );
1473 Some(button)
1474 })
1475 .collect_vec()
1476 })
1477 }
1478
1479 fn layout_code_actions_indicator(
1480 &self,
1481 line_height: Pixels,
1482 newest_selection_head: DisplayPoint,
1483 scroll_pixel_position: gpui::Point<Pixels>,
1484 gutter_dimensions: &GutterDimensions,
1485 gutter_hitbox: &Hitbox,
1486 cx: &mut WindowContext,
1487 ) -> Option<AnyElement> {
1488 let mut active = false;
1489 let mut button = None;
1490 let row = newest_selection_head.row();
1491 self.editor.update(cx, |editor, cx| {
1492 if let Some(crate::ContextMenu::CodeActions(CodeActionsMenu {
1493 deployed_from_indicator,
1494 ..
1495 })) = editor.context_menu.read().as_ref()
1496 {
1497 active = deployed_from_indicator.map_or(true, |indicator_row| indicator_row == row);
1498 };
1499 button = editor.render_code_actions_indicator(&self.style, row, active, cx);
1500 });
1501
1502 let button = prepaint_gutter_button(
1503 button?,
1504 row,
1505 line_height,
1506 gutter_dimensions,
1507 scroll_pixel_position,
1508 gutter_hitbox,
1509 cx,
1510 );
1511
1512 Some(button)
1513 }
1514
1515 fn get_participant_color(
1516 participant_index: Option<ParticipantIndex>,
1517 cx: &WindowContext,
1518 ) -> PlayerColor {
1519 if let Some(index) = participant_index {
1520 cx.theme().players().color_for_participant(index.0)
1521 } else {
1522 cx.theme().players().absent()
1523 }
1524 }
1525
1526 fn calculate_relative_line_numbers(
1527 &self,
1528 snapshot: &EditorSnapshot,
1529 rows: &Range<DisplayRow>,
1530 relative_to: Option<DisplayRow>,
1531 ) -> HashMap<DisplayRow, DisplayRowDelta> {
1532 let mut relative_rows: HashMap<DisplayRow, DisplayRowDelta> = Default::default();
1533 let Some(relative_to) = relative_to else {
1534 return relative_rows;
1535 };
1536
1537 let start = rows.start.min(relative_to);
1538 let end = rows.end.max(relative_to);
1539
1540 let buffer_rows = snapshot
1541 .buffer_rows(start)
1542 .take(1 + end.minus(start) as usize)
1543 .collect::<Vec<_>>();
1544
1545 let head_idx = relative_to.minus(start);
1546 let mut delta = 1;
1547 let mut i = head_idx + 1;
1548 while i < buffer_rows.len() as u32 {
1549 if buffer_rows[i as usize].is_some() {
1550 if rows.contains(&DisplayRow(i + start.0)) {
1551 relative_rows.insert(DisplayRow(i + start.0), delta);
1552 }
1553 delta += 1;
1554 }
1555 i += 1;
1556 }
1557 delta = 1;
1558 i = head_idx.min(buffer_rows.len() as u32 - 1);
1559 while i > 0 && buffer_rows[i as usize].is_none() {
1560 i -= 1;
1561 }
1562
1563 while i > 0 {
1564 i -= 1;
1565 if buffer_rows[i as usize].is_some() {
1566 if rows.contains(&DisplayRow(i + start.0)) {
1567 relative_rows.insert(DisplayRow(i + start.0), delta);
1568 }
1569 delta += 1;
1570 }
1571 }
1572
1573 relative_rows
1574 }
1575
1576 fn layout_line_numbers(
1577 &self,
1578 rows: Range<DisplayRow>,
1579 buffer_rows: impl Iterator<Item = Option<MultiBufferRow>>,
1580 active_rows: &BTreeMap<DisplayRow, bool>,
1581 newest_selection_head: Option<DisplayPoint>,
1582 snapshot: &EditorSnapshot,
1583 cx: &WindowContext,
1584 ) -> (
1585 Vec<Option<ShapedLine>>,
1586 Vec<Option<(FoldStatus, MultiBufferRow, bool)>>,
1587 ) {
1588 let editor = self.editor.read(cx);
1589 let is_singleton = editor.is_singleton(cx);
1590 let newest_selection_head = newest_selection_head.unwrap_or_else(|| {
1591 let newest = editor.selections.newest::<Point>(cx);
1592 SelectionLayout::new(
1593 newest,
1594 editor.selections.line_mode,
1595 editor.cursor_shape,
1596 &snapshot.display_snapshot,
1597 true,
1598 true,
1599 None,
1600 )
1601 .head
1602 });
1603 let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
1604 let include_line_numbers =
1605 EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode == EditorMode::Full;
1606 let include_fold_statuses =
1607 EditorSettings::get_global(cx).gutter.folds && snapshot.mode == EditorMode::Full;
1608 let mut shaped_line_numbers = Vec::with_capacity(rows.len());
1609 let mut fold_statuses = Vec::with_capacity(rows.len());
1610 let mut line_number = String::new();
1611 let is_relative = EditorSettings::get_global(cx).relative_line_numbers;
1612 let relative_to = if is_relative {
1613 Some(newest_selection_head.row())
1614 } else {
1615 None
1616 };
1617
1618 let relative_rows = self.calculate_relative_line_numbers(snapshot, &rows, relative_to);
1619
1620 for (ix, row) in buffer_rows.into_iter().enumerate() {
1621 let display_row = DisplayRow(rows.start.0 + ix as u32);
1622 let (active, color) = if active_rows.contains_key(&display_row) {
1623 (true, cx.theme().colors().editor_active_line_number)
1624 } else {
1625 (false, cx.theme().colors().editor_line_number)
1626 };
1627 if let Some(multibuffer_row) = row {
1628 if include_line_numbers {
1629 line_number.clear();
1630 let default_number = multibuffer_row.0 + 1;
1631 let number = relative_rows
1632 .get(&DisplayRow(ix as u32 + rows.start.0))
1633 .unwrap_or(&default_number);
1634 write!(&mut line_number, "{number}").unwrap();
1635 let run = TextRun {
1636 len: line_number.len(),
1637 font: self.style.text.font(),
1638 color,
1639 background_color: None,
1640 underline: None,
1641 strikethrough: None,
1642 };
1643 let shaped_line = cx
1644 .text_system()
1645 .shape_line(line_number.clone().into(), font_size, &[run])
1646 .unwrap();
1647 shaped_line_numbers.push(Some(shaped_line));
1648 }
1649 if include_fold_statuses {
1650 fold_statuses.push(
1651 is_singleton
1652 .then(|| {
1653 snapshot
1654 .fold_for_line(multibuffer_row)
1655 .map(|fold_status| (fold_status, multibuffer_row, active))
1656 })
1657 .flatten(),
1658 )
1659 }
1660 } else {
1661 fold_statuses.push(None);
1662 shaped_line_numbers.push(None);
1663 }
1664 }
1665
1666 (shaped_line_numbers, fold_statuses)
1667 }
1668
1669 fn layout_lines(
1670 &self,
1671 rows: Range<DisplayRow>,
1672 line_number_layouts: &[Option<ShapedLine>],
1673 snapshot: &EditorSnapshot,
1674 cx: &WindowContext,
1675 ) -> Vec<LineWithInvisibles> {
1676 if rows.start >= rows.end {
1677 return Vec::new();
1678 }
1679
1680 // Show the placeholder when the editor is empty
1681 if snapshot.is_empty() {
1682 let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
1683 let placeholder_color = cx.theme().colors().text_placeholder;
1684 let placeholder_text = snapshot.placeholder_text();
1685
1686 let placeholder_lines = placeholder_text
1687 .as_ref()
1688 .map_or("", AsRef::as_ref)
1689 .split('\n')
1690 .skip(rows.start.0 as usize)
1691 .chain(iter::repeat(""))
1692 .take(rows.len());
1693 placeholder_lines
1694 .filter_map(move |line| {
1695 let run = TextRun {
1696 len: line.len(),
1697 font: self.style.text.font(),
1698 color: placeholder_color,
1699 background_color: None,
1700 underline: Default::default(),
1701 strikethrough: None,
1702 };
1703 cx.text_system()
1704 .shape_line(line.to_string().into(), font_size, &[run])
1705 .log_err()
1706 })
1707 .map(|line| LineWithInvisibles {
1708 line,
1709 invisibles: Vec::new(),
1710 })
1711 .collect()
1712 } else {
1713 let chunks = snapshot.highlighted_chunks(rows.clone(), true, &self.style);
1714 LineWithInvisibles::from_chunks(
1715 chunks,
1716 &self.style.text,
1717 MAX_LINE_LEN,
1718 rows.len(),
1719 line_number_layouts,
1720 snapshot.mode,
1721 cx,
1722 )
1723 }
1724 }
1725
1726 #[allow(clippy::too_many_arguments)]
1727 fn build_blocks(
1728 &self,
1729 rows: Range<DisplayRow>,
1730 snapshot: &EditorSnapshot,
1731 hitbox: &Hitbox,
1732 text_hitbox: &Hitbox,
1733 scroll_width: &mut Pixels,
1734 gutter_dimensions: &GutterDimensions,
1735 em_width: Pixels,
1736 text_x: Pixels,
1737 line_height: Pixels,
1738 line_layouts: &[LineWithInvisibles],
1739 cx: &mut WindowContext,
1740 ) -> Vec<BlockLayout> {
1741 let mut block_id = 0;
1742 let (fixed_blocks, non_fixed_blocks) = snapshot
1743 .blocks_in_range(rows.clone())
1744 .partition::<Vec<_>, _>(|(_, block)| match block {
1745 TransformBlock::ExcerptHeader { .. } => false,
1746 TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
1747 });
1748
1749 let render_block = |block: &TransformBlock,
1750 available_space: Size<AvailableSpace>,
1751 block_id: usize,
1752 block_row_start: DisplayRow,
1753 cx: &mut WindowContext| {
1754 let mut element = match block {
1755 TransformBlock::Custom(block) => {
1756 let align_to = block
1757 .position()
1758 .to_point(&snapshot.buffer_snapshot)
1759 .to_display_point(snapshot);
1760 let anchor_x = text_x
1761 + if rows.contains(&align_to.row()) {
1762 line_layouts[align_to.row().minus(rows.start) as usize]
1763 .line
1764 .x_for_index(align_to.column() as usize)
1765 } else {
1766 layout_line(align_to.row(), snapshot, &self.style, cx)
1767 .unwrap()
1768 .x_for_index(align_to.column() as usize)
1769 };
1770
1771 block.render(&mut BlockContext {
1772 context: cx,
1773 anchor_x,
1774 gutter_dimensions,
1775 line_height,
1776 em_width,
1777 block_id,
1778 max_width: text_hitbox.size.width.max(*scroll_width),
1779 editor_style: &self.style,
1780 })
1781 }
1782
1783 TransformBlock::ExcerptHeader {
1784 buffer,
1785 range,
1786 starts_new_buffer,
1787 height,
1788 id,
1789 ..
1790 } => {
1791 let include_root = self
1792 .editor
1793 .read(cx)
1794 .project
1795 .as_ref()
1796 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
1797 .unwrap_or_default();
1798
1799 #[derive(Clone)]
1800 struct JumpData {
1801 position: Point,
1802 anchor: text::Anchor,
1803 path: ProjectPath,
1804 line_offset_from_top: u32,
1805 }
1806
1807 let jump_data = project::File::from_dyn(buffer.file()).map(|file| {
1808 let jump_path = ProjectPath {
1809 worktree_id: file.worktree_id(cx),
1810 path: file.path.clone(),
1811 };
1812 let jump_anchor = range
1813 .primary
1814 .as_ref()
1815 .map_or(range.context.start, |primary| primary.start);
1816
1817 let excerpt_start = range.context.start;
1818 let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
1819 let offset_from_excerpt_start = if jump_anchor == excerpt_start {
1820 0
1821 } else {
1822 let excerpt_start_row =
1823 language::ToPoint::to_point(&jump_anchor, buffer).row;
1824 jump_position.row - excerpt_start_row
1825 };
1826
1827 let line_offset_from_top =
1828 block_row_start.0 + *height as u32 + offset_from_excerpt_start
1829 - snapshot
1830 .scroll_anchor
1831 .scroll_position(&snapshot.display_snapshot)
1832 .y as u32;
1833
1834 JumpData {
1835 position: jump_position,
1836 anchor: jump_anchor,
1837 path: jump_path,
1838 line_offset_from_top,
1839 }
1840 });
1841
1842 let element = if *starts_new_buffer {
1843 let path = buffer.resolve_file_path(cx, include_root);
1844 let mut filename = None;
1845 let mut parent_path = None;
1846 // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
1847 if let Some(path) = path {
1848 filename = path.file_name().map(|f| f.to_string_lossy().to_string());
1849 parent_path = path
1850 .parent()
1851 .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
1852 }
1853
1854 v_flex()
1855 .id(("path header container", block_id))
1856 .size_full()
1857 .justify_center()
1858 .p(gpui::px(6.))
1859 .child(
1860 h_flex()
1861 .id("path header block")
1862 .size_full()
1863 .pl(gpui::px(12.))
1864 .pr(gpui::px(8.))
1865 .rounded_md()
1866 .shadow_md()
1867 .border_1()
1868 .border_color(cx.theme().colors().border)
1869 .bg(cx.theme().colors().editor_subheader_background)
1870 .justify_between()
1871 .hover(|style| style.bg(cx.theme().colors().element_hover))
1872 .child(
1873 h_flex().gap_3().child(
1874 h_flex()
1875 .gap_2()
1876 .child(
1877 filename
1878 .map(SharedString::from)
1879 .unwrap_or_else(|| "untitled".into()),
1880 )
1881 .when_some(parent_path, |then, path| {
1882 then.child(
1883 div().child(path).text_color(
1884 cx.theme().colors().text_muted,
1885 ),
1886 )
1887 }),
1888 ),
1889 )
1890 .when_some(jump_data.clone(), |this, jump_data| {
1891 this.cursor_pointer()
1892 .tooltip(|cx| {
1893 Tooltip::for_action(
1894 "Jump to File",
1895 &OpenExcerpts,
1896 cx,
1897 )
1898 })
1899 .on_mouse_down(MouseButton::Left, |_, cx| {
1900 cx.stop_propagation()
1901 })
1902 .on_click(cx.listener_for(&self.editor, {
1903 move |editor, _, cx| {
1904 editor.jump(
1905 jump_data.path.clone(),
1906 jump_data.position,
1907 jump_data.anchor,
1908 jump_data.line_offset_from_top,
1909 cx,
1910 );
1911 }
1912 }))
1913 }),
1914 )
1915 } else {
1916 v_flex()
1917 .id(("collapsed context", block_id))
1918 .size_full()
1919 .child(
1920 div()
1921 .flex()
1922 .v_flex()
1923 .justify_start()
1924 .id("jump to collapsed context")
1925 .w(relative(1.0))
1926 .h_full()
1927 .child(
1928 div()
1929 .h_px()
1930 .w_full()
1931 .bg(cx.theme().colors().border_variant)
1932 .group_hover("excerpt-jump-action", |style| {
1933 style.bg(cx.theme().colors().border)
1934 }),
1935 ),
1936 )
1937 .child(
1938 h_flex()
1939 .justify_end()
1940 .flex_none()
1941 .w(
1942 gutter_dimensions.width - (gutter_dimensions.left_padding), // + gutter_dimensions.right_padding)
1943 )
1944 .h_full()
1945 .child(
1946 ButtonLike::new("expand-icon")
1947 .style(ButtonStyle::Transparent)
1948 .child(
1949 svg()
1950 .path(IconName::ExpandVertical.path())
1951 .size(IconSize::XSmall.rems())
1952 .text_color(
1953 cx.theme().colors().editor_line_number,
1954 )
1955 .group("")
1956 .hover(|style| {
1957 style.text_color(
1958 cx.theme()
1959 .colors()
1960 .editor_active_line_number,
1961 )
1962 }),
1963 )
1964 .on_click(cx.listener_for(&self.editor, {
1965 let id = *id;
1966 move |editor, _, cx| {
1967 editor.expand_excerpt(id, cx);
1968 }
1969 }))
1970 .tooltip({
1971 move |cx| {
1972 Tooltip::for_action(
1973 "Expand Excerpt",
1974 &ExpandExcerpts { lines: 0 },
1975 cx,
1976 )
1977 }
1978 }),
1979 ),
1980 )
1981 .group("excerpt-jump-action")
1982 .cursor_pointer()
1983 .when_some(jump_data.clone(), |this, jump_data| {
1984 this.on_click(cx.listener_for(&self.editor, {
1985 let path = jump_data.path.clone();
1986 move |editor, _, cx| {
1987 cx.stop_propagation();
1988
1989 editor.jump(
1990 path.clone(),
1991 jump_data.position,
1992 jump_data.anchor,
1993 jump_data.line_offset_from_top,
1994 cx,
1995 );
1996 }
1997 }))
1998 .tooltip(move |cx| {
1999 Tooltip::for_action(
2000 format!(
2001 "Jump to {}:L{}",
2002 jump_data.path.path.display(),
2003 jump_data.position.row + 1
2004 ),
2005 &OpenExcerpts,
2006 cx,
2007 )
2008 })
2009 })
2010 };
2011 element.into_any()
2012 }
2013 };
2014
2015 let size = element.layout_as_root(available_space, cx);
2016 (element, size)
2017 };
2018
2019 let mut fixed_block_max_width = Pixels::ZERO;
2020 let mut blocks = Vec::new();
2021 for (row, block) in fixed_blocks {
2022 let available_space = size(
2023 AvailableSpace::MinContent,
2024 AvailableSpace::Definite(block.height() as f32 * line_height),
2025 );
2026 let (element, element_size) = render_block(block, available_space, block_id, row, cx);
2027 block_id += 1;
2028 fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
2029 blocks.push(BlockLayout {
2030 row,
2031 element,
2032 available_space,
2033 style: BlockStyle::Fixed,
2034 });
2035 }
2036 for (row, block) in non_fixed_blocks {
2037 let style = match block {
2038 TransformBlock::Custom(block) => block.style(),
2039 TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
2040 };
2041 let width = match style {
2042 BlockStyle::Sticky => hitbox.size.width,
2043 BlockStyle::Flex => hitbox
2044 .size
2045 .width
2046 .max(fixed_block_max_width)
2047 .max(gutter_dimensions.width + *scroll_width),
2048 BlockStyle::Fixed => unreachable!(),
2049 };
2050 let available_space = size(
2051 AvailableSpace::Definite(width),
2052 AvailableSpace::Definite(block.height() as f32 * line_height),
2053 );
2054 let (element, _) = render_block(block, available_space, block_id, row, cx);
2055 block_id += 1;
2056 blocks.push(BlockLayout {
2057 row,
2058 element,
2059 available_space,
2060 style,
2061 });
2062 }
2063
2064 *scroll_width = (*scroll_width).max(fixed_block_max_width - gutter_dimensions.width);
2065 blocks
2066 }
2067
2068 fn layout_blocks(
2069 &self,
2070 blocks: &mut Vec<BlockLayout>,
2071 hitbox: &Hitbox,
2072 line_height: Pixels,
2073 scroll_pixel_position: gpui::Point<Pixels>,
2074 cx: &mut WindowContext,
2075 ) {
2076 for block in blocks {
2077 let mut origin = hitbox.origin
2078 + point(
2079 Pixels::ZERO,
2080 block.row.as_f32() * line_height - scroll_pixel_position.y,
2081 );
2082 if !matches!(block.style, BlockStyle::Sticky) {
2083 origin += point(-scroll_pixel_position.x, Pixels::ZERO);
2084 }
2085 block
2086 .element
2087 .prepaint_as_root(origin, block.available_space, cx);
2088 }
2089 }
2090
2091 #[allow(clippy::too_many_arguments)]
2092 fn layout_context_menu(
2093 &self,
2094 line_height: Pixels,
2095 hitbox: &Hitbox,
2096 text_hitbox: &Hitbox,
2097 content_origin: gpui::Point<Pixels>,
2098 start_row: DisplayRow,
2099 scroll_pixel_position: gpui::Point<Pixels>,
2100 line_layouts: &[LineWithInvisibles],
2101 newest_selection_head: DisplayPoint,
2102 gutter_overshoot: Pixels,
2103 cx: &mut WindowContext,
2104 ) -> bool {
2105 let max_height = cmp::min(
2106 12. * line_height,
2107 cmp::max(3. * line_height, (hitbox.size.height - line_height) / 2.),
2108 );
2109 let Some((position, mut context_menu)) = self.editor.update(cx, |editor, cx| {
2110 if editor.context_menu_visible() {
2111 editor.render_context_menu(newest_selection_head, &self.style, max_height, cx)
2112 } else {
2113 None
2114 }
2115 }) else {
2116 return false;
2117 };
2118
2119 let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
2120 let context_menu_size = context_menu.layout_as_root(available_space, cx);
2121
2122 let (x, y) = match position {
2123 crate::ContextMenuOrigin::EditorPoint(point) => {
2124 let cursor_row_layout = &line_layouts[point.row().minus(start_row) as usize].line;
2125 let x = cursor_row_layout.x_for_index(point.column() as usize)
2126 - scroll_pixel_position.x;
2127 let y = point.row().next_row().as_f32() * line_height - scroll_pixel_position.y;
2128 (x, y)
2129 }
2130 crate::ContextMenuOrigin::GutterIndicator(row) => {
2131 // Context menu was spawned via a click on a gutter. Ensure it's a bit closer to the indicator than just a plain first column of the
2132 // text field.
2133 let x = -gutter_overshoot;
2134 let y = row.next_row().as_f32() * line_height - scroll_pixel_position.y;
2135 (x, y)
2136 }
2137 };
2138
2139 let mut list_origin = content_origin + point(x, y);
2140 let list_width = context_menu_size.width;
2141 let list_height = context_menu_size.height;
2142
2143 // Snap the right edge of the list to the right edge of the window if
2144 // its horizontal bounds overflow.
2145 if list_origin.x + list_width > cx.viewport_size().width {
2146 list_origin.x = (cx.viewport_size().width - list_width).max(Pixels::ZERO);
2147 }
2148
2149 if list_origin.y + list_height > text_hitbox.lower_right().y {
2150 list_origin.y -= line_height + list_height;
2151 }
2152
2153 cx.defer_draw(context_menu, list_origin, 1);
2154 true
2155 }
2156
2157 fn layout_mouse_context_menu(&self, cx: &mut WindowContext) -> Option<AnyElement> {
2158 let mouse_context_menu = self.editor.read(cx).mouse_context_menu.as_ref()?;
2159 let mut element = deferred(
2160 anchored()
2161 .position(mouse_context_menu.position)
2162 .child(mouse_context_menu.context_menu.clone())
2163 .anchor(AnchorCorner::TopLeft)
2164 .snap_to_window(),
2165 )
2166 .with_priority(1)
2167 .into_any();
2168
2169 element.prepaint_as_root(gpui::Point::default(), AvailableSpace::min_size(), cx);
2170 Some(element)
2171 }
2172
2173 #[allow(clippy::too_many_arguments)]
2174 fn layout_hover_popovers(
2175 &self,
2176 snapshot: &EditorSnapshot,
2177 hitbox: &Hitbox,
2178 text_hitbox: &Hitbox,
2179 visible_display_row_range: Range<DisplayRow>,
2180 content_origin: gpui::Point<Pixels>,
2181 scroll_pixel_position: gpui::Point<Pixels>,
2182 line_layouts: &[LineWithInvisibles],
2183 line_height: Pixels,
2184 em_width: Pixels,
2185 cx: &mut WindowContext,
2186 ) {
2187 struct MeasuredHoverPopover {
2188 element: AnyElement,
2189 size: Size<Pixels>,
2190 horizontal_offset: Pixels,
2191 }
2192
2193 let max_size = size(
2194 (120. * em_width) // Default size
2195 .min(hitbox.size.width / 2.) // Shrink to half of the editor width
2196 .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
2197 (16. * line_height) // Default size
2198 .min(hitbox.size.height / 2.) // Shrink to half of the editor height
2199 .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
2200 );
2201
2202 let hover_popovers = self.editor.update(cx, |editor, cx| {
2203 editor.hover_state.render(
2204 &snapshot,
2205 &self.style,
2206 visible_display_row_range.clone(),
2207 max_size,
2208 editor.workspace.as_ref().map(|(w, _)| w.clone()),
2209 cx,
2210 )
2211 });
2212 let Some((position, hover_popovers)) = hover_popovers else {
2213 return;
2214 };
2215
2216 let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
2217
2218 // This is safe because we check on layout whether the required row is available
2219 let hovered_row_layout =
2220 &line_layouts[position.row().minus(visible_display_row_range.start) as usize].line;
2221
2222 // Compute Hovered Point
2223 let x =
2224 hovered_row_layout.x_for_index(position.column() as usize) - scroll_pixel_position.x;
2225 let y = position.row().as_f32() * line_height - scroll_pixel_position.y;
2226 let hovered_point = content_origin + point(x, y);
2227
2228 let mut overall_height = Pixels::ZERO;
2229 let mut measured_hover_popovers = Vec::new();
2230 for mut hover_popover in hover_popovers {
2231 let size = hover_popover.layout_as_root(available_space, cx);
2232 let horizontal_offset =
2233 (text_hitbox.upper_right().x - (hovered_point.x + size.width)).min(Pixels::ZERO);
2234
2235 overall_height += HOVER_POPOVER_GAP + size.height;
2236
2237 measured_hover_popovers.push(MeasuredHoverPopover {
2238 element: hover_popover,
2239 size,
2240 horizontal_offset,
2241 });
2242 }
2243 overall_height += HOVER_POPOVER_GAP;
2244
2245 fn draw_occluder(width: Pixels, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
2246 let mut occlusion = div()
2247 .size_full()
2248 .occlude()
2249 .on_mouse_move(|_, cx| cx.stop_propagation())
2250 .into_any_element();
2251 occlusion.layout_as_root(size(width, HOVER_POPOVER_GAP).into(), cx);
2252 cx.defer_draw(occlusion, origin, 2);
2253 }
2254
2255 if hovered_point.y > overall_height {
2256 // There is enough space above. Render popovers above the hovered point
2257 let mut current_y = hovered_point.y;
2258 for (position, popover) in measured_hover_popovers.into_iter().with_position() {
2259 let size = popover.size;
2260 let popover_origin = point(
2261 hovered_point.x + popover.horizontal_offset,
2262 current_y - size.height,
2263 );
2264
2265 cx.defer_draw(popover.element, popover_origin, 2);
2266 if position != itertools::Position::Last {
2267 let origin = point(popover_origin.x, popover_origin.y - HOVER_POPOVER_GAP);
2268 draw_occluder(size.width, origin, cx);
2269 }
2270
2271 current_y = popover_origin.y - HOVER_POPOVER_GAP;
2272 }
2273 } else {
2274 // There is not enough space above. Render popovers below the hovered point
2275 let mut current_y = hovered_point.y + line_height;
2276 for (position, popover) in measured_hover_popovers.into_iter().with_position() {
2277 let size = popover.size;
2278 let popover_origin = point(hovered_point.x + popover.horizontal_offset, current_y);
2279
2280 cx.defer_draw(popover.element, popover_origin, 2);
2281 if position != itertools::Position::Last {
2282 let origin = point(popover_origin.x, popover_origin.y + size.height);
2283 draw_occluder(size.width, origin, cx);
2284 }
2285
2286 current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
2287 }
2288 }
2289 }
2290
2291 fn paint_background(&self, layout: &EditorLayout, cx: &mut WindowContext) {
2292 cx.paint_layer(layout.hitbox.bounds, |cx| {
2293 let scroll_top = layout.position_map.snapshot.scroll_position().y;
2294 let gutter_bg = cx.theme().colors().editor_gutter_background;
2295 cx.paint_quad(fill(layout.gutter_hitbox.bounds, gutter_bg));
2296 cx.paint_quad(fill(layout.text_hitbox.bounds, self.style.background));
2297
2298 if let EditorMode::Full = layout.mode {
2299 let mut active_rows = layout.active_rows.iter().peekable();
2300 while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
2301 let mut end_row = start_row.0;
2302 while active_rows
2303 .peek()
2304 .map_or(false, |(active_row, has_selection)| {
2305 active_row.0 == end_row + 1
2306 && *has_selection == contains_non_empty_selection
2307 })
2308 {
2309 active_rows.next().unwrap();
2310 end_row += 1;
2311 }
2312
2313 if !contains_non_empty_selection {
2314 let highlight_h_range =
2315 match layout.position_map.snapshot.current_line_highlight {
2316 CurrentLineHighlight::Gutter => Some(Range {
2317 start: layout.hitbox.left(),
2318 end: layout.gutter_hitbox.right(),
2319 }),
2320 CurrentLineHighlight::Line => Some(Range {
2321 start: layout.text_hitbox.bounds.left(),
2322 end: layout.text_hitbox.bounds.right(),
2323 }),
2324 CurrentLineHighlight::All => Some(Range {
2325 start: layout.hitbox.left(),
2326 end: layout.hitbox.right(),
2327 }),
2328 CurrentLineHighlight::None => None,
2329 };
2330 if let Some(range) = highlight_h_range {
2331 let active_line_bg = cx.theme().colors().editor_active_line_background;
2332 let bounds = Bounds {
2333 origin: point(
2334 range.start,
2335 layout.hitbox.origin.y
2336 + (start_row.as_f32() - scroll_top)
2337 * layout.position_map.line_height,
2338 ),
2339 size: size(
2340 range.end - range.start,
2341 layout.position_map.line_height
2342 * (end_row - start_row.0 + 1) as f32,
2343 ),
2344 };
2345 cx.paint_quad(fill(bounds, active_line_bg));
2346 }
2347 }
2348 }
2349
2350 let mut paint_highlight =
2351 |highlight_row_start: DisplayRow, highlight_row_end: DisplayRow, color| {
2352 let origin = point(
2353 layout.hitbox.origin.x,
2354 layout.hitbox.origin.y
2355 + (highlight_row_start.as_f32() - scroll_top)
2356 * layout.position_map.line_height,
2357 );
2358 let size = size(
2359 layout.hitbox.size.width,
2360 layout.position_map.line_height
2361 * highlight_row_end.next_row().minus(highlight_row_start) as f32,
2362 );
2363 cx.paint_quad(fill(Bounds { origin, size }, color));
2364 };
2365
2366 let mut current_paint: Option<(Hsla, Range<DisplayRow>)> = None;
2367 for (&new_row, &new_color) in &layout.highlighted_rows {
2368 match &mut current_paint {
2369 Some((current_color, current_range)) => {
2370 let current_color = *current_color;
2371 let new_range_started = current_color != new_color
2372 || current_range.end.next_row() != new_row;
2373 if new_range_started {
2374 paint_highlight(
2375 current_range.start,
2376 current_range.end,
2377 current_color,
2378 );
2379 current_paint = Some((new_color, new_row..new_row));
2380 continue;
2381 } else {
2382 current_range.end = current_range.end.next_row();
2383 }
2384 }
2385 None => current_paint = Some((new_color, new_row..new_row)),
2386 };
2387 }
2388 if let Some((color, range)) = current_paint {
2389 paint_highlight(range.start, range.end, color);
2390 }
2391
2392 let scroll_left =
2393 layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
2394
2395 for (wrap_position, active) in layout.wrap_guides.iter() {
2396 let x = (layout.text_hitbox.origin.x
2397 + *wrap_position
2398 + layout.position_map.em_width / 2.)
2399 - scroll_left;
2400
2401 let show_scrollbars = layout
2402 .scrollbar_layout
2403 .as_ref()
2404 .map_or(false, |scrollbar| scrollbar.visible);
2405 if x < layout.text_hitbox.origin.x
2406 || (show_scrollbars && x > self.scrollbar_left(&layout.hitbox.bounds))
2407 {
2408 continue;
2409 }
2410
2411 let color = if *active {
2412 cx.theme().colors().editor_active_wrap_guide
2413 } else {
2414 cx.theme().colors().editor_wrap_guide
2415 };
2416 cx.paint_quad(fill(
2417 Bounds {
2418 origin: point(x, layout.text_hitbox.origin.y),
2419 size: size(px(1.), layout.text_hitbox.size.height),
2420 },
2421 color,
2422 ));
2423 }
2424 }
2425 })
2426 }
2427
2428 fn paint_gutter(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
2429 let line_height = layout.position_map.line_height;
2430
2431 let scroll_position = layout.position_map.snapshot.scroll_position();
2432 let scroll_top = scroll_position.y * line_height;
2433
2434 cx.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox);
2435 for (_, hunk_hitbox) in &layout.display_hunks {
2436 if let Some(hunk_hitbox) = hunk_hitbox {
2437 cx.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox);
2438 }
2439 }
2440
2441 let show_git_gutter = matches!(
2442 ProjectSettings::get_global(cx).git.git_gutter,
2443 Some(GitGutterSetting::TrackedFiles)
2444 );
2445 if show_git_gutter {
2446 Self::paint_diff_hunks(layout.gutter_hitbox.bounds, layout, cx)
2447 }
2448
2449 if layout.blamed_display_rows.is_some() {
2450 self.paint_blamed_display_rows(layout, cx);
2451 }
2452
2453 for (ix, line) in layout.line_numbers.iter().enumerate() {
2454 if let Some(line) = line {
2455 let line_origin = layout.gutter_hitbox.origin
2456 + point(
2457 layout.gutter_hitbox.size.width
2458 - line.width
2459 - layout.gutter_dimensions.right_padding,
2460 ix as f32 * line_height - (scroll_top % line_height),
2461 );
2462
2463 line.paint(line_origin, line_height, cx).log_err();
2464 }
2465 }
2466
2467 cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
2468 cx.with_element_namespace("gutter_fold_indicators", |cx| {
2469 for fold_indicator in layout.fold_indicators.iter_mut().flatten() {
2470 fold_indicator.paint(cx);
2471 }
2472 });
2473
2474 for test_indicators in layout.test_indicators.iter_mut() {
2475 test_indicators.paint(cx);
2476 }
2477
2478 if let Some(indicator) = layout.code_actions_indicator.as_mut() {
2479 indicator.paint(cx);
2480 }
2481 });
2482 }
2483
2484 fn paint_diff_hunks(
2485 gutter_bounds: Bounds<Pixels>,
2486 layout: &EditorLayout,
2487 cx: &mut WindowContext,
2488 ) {
2489 if layout.display_hunks.is_empty() {
2490 return;
2491 }
2492
2493 let line_height = layout.position_map.line_height;
2494 cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
2495 for (hunk, hitbox) in &layout.display_hunks {
2496 let hunk_to_paint = match hunk {
2497 DisplayDiffHunk::Folded { .. } => {
2498 let hunk_bounds = Self::diff_hunk_bounds(
2499 &layout.position_map.snapshot,
2500 line_height,
2501 gutter_bounds,
2502 &hunk,
2503 );
2504 Some((
2505 hunk_bounds,
2506 cx.theme().status().modified,
2507 Corners::all(1. * line_height),
2508 ))
2509 }
2510 DisplayDiffHunk::Unfolded { status, .. } => {
2511 hitbox.as_ref().map(|hunk_hitbox| match status {
2512 DiffHunkStatus::Added => (
2513 hunk_hitbox.bounds,
2514 cx.theme().status().created,
2515 Corners::all(0.05 * line_height),
2516 ),
2517 DiffHunkStatus::Modified => (
2518 hunk_hitbox.bounds,
2519 cx.theme().status().modified,
2520 Corners::all(0.05 * line_height),
2521 ),
2522 DiffHunkStatus::Removed => (
2523 hunk_hitbox.bounds,
2524 cx.theme().status().deleted,
2525 Corners::all(1. * line_height),
2526 ),
2527 })
2528 }
2529 };
2530
2531 if let Some((hunk_bounds, background_color, corner_radii)) = hunk_to_paint {
2532 cx.paint_quad(quad(
2533 hunk_bounds,
2534 corner_radii,
2535 background_color,
2536 Edges::default(),
2537 transparent_black(),
2538 ));
2539 }
2540 }
2541 });
2542 }
2543
2544 fn diff_hunk_bounds(
2545 snapshot: &EditorSnapshot,
2546 line_height: Pixels,
2547 bounds: Bounds<Pixels>,
2548 hunk: &DisplayDiffHunk,
2549 ) -> Bounds<Pixels> {
2550 let scroll_position = snapshot.scroll_position();
2551 let scroll_top = scroll_position.y * line_height;
2552
2553 match hunk {
2554 DisplayDiffHunk::Folded { display_row, .. } => {
2555 let start_y = display_row.as_f32() * line_height - scroll_top;
2556 let end_y = start_y + line_height;
2557
2558 let width = 0.275 * line_height;
2559 let highlight_origin = bounds.origin + point(-width, start_y);
2560 let highlight_size = size(width * 2., end_y - start_y);
2561 Bounds::new(highlight_origin, highlight_size)
2562 }
2563 DisplayDiffHunk::Unfolded {
2564 display_row_range,
2565 status,
2566 ..
2567 } => match status {
2568 DiffHunkStatus::Added | DiffHunkStatus::Modified => {
2569 let start_row = display_row_range.start;
2570 let end_row = display_row_range.end;
2571 // If we're in a multibuffer, row range span might include an
2572 // excerpt header, so if we were to draw the marker straight away,
2573 // the hunk might include the rows of that header.
2574 // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap.
2575 // Instead, we simply check whether the range we're dealing with includes
2576 // any excerpt headers and if so, we stop painting the diff hunk on the first row of that header.
2577 let end_row_in_current_excerpt = snapshot
2578 .blocks_in_range(start_row..end_row)
2579 .find_map(|(start_row, block)| {
2580 if matches!(block, TransformBlock::ExcerptHeader { .. }) {
2581 Some(start_row)
2582 } else {
2583 None
2584 }
2585 })
2586 .unwrap_or(end_row);
2587
2588 let start_y = start_row.as_f32() * line_height - scroll_top;
2589 let end_y = end_row_in_current_excerpt.as_f32() * line_height - scroll_top;
2590
2591 let width = 0.275 * line_height;
2592 let highlight_origin = bounds.origin + point(-width, start_y);
2593 let highlight_size = size(width * 2., end_y - start_y);
2594 Bounds::new(highlight_origin, highlight_size)
2595 }
2596 DiffHunkStatus::Removed => {
2597 let row = display_row_range.start;
2598
2599 let offset = line_height / 2.;
2600 let start_y = row.as_f32() * line_height - offset - scroll_top;
2601 let end_y = start_y + line_height;
2602
2603 let width = 0.35 * line_height;
2604 let highlight_origin = bounds.origin + point(-width, start_y);
2605 let highlight_size = size(width * 2., end_y - start_y);
2606 Bounds::new(highlight_origin, highlight_size)
2607 }
2608 },
2609 }
2610 }
2611
2612 fn paint_blamed_display_rows(&self, layout: &mut EditorLayout, cx: &mut WindowContext) {
2613 let Some(blamed_display_rows) = layout.blamed_display_rows.take() else {
2614 return;
2615 };
2616
2617 cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
2618 for mut blame_element in blamed_display_rows.into_iter() {
2619 blame_element.paint(cx);
2620 }
2621 })
2622 }
2623
2624 fn paint_text(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
2625 cx.with_content_mask(
2626 Some(ContentMask {
2627 bounds: layout.text_hitbox.bounds,
2628 }),
2629 |cx| {
2630 let cursor_style = if self
2631 .editor
2632 .read(cx)
2633 .hovered_link_state
2634 .as_ref()
2635 .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
2636 {
2637 CursorStyle::PointingHand
2638 } else {
2639 CursorStyle::IBeam
2640 };
2641 cx.set_cursor_style(cursor_style, &layout.text_hitbox);
2642
2643 cx.with_element_namespace("folds", |cx| self.paint_folds(layout, cx));
2644 let invisible_display_ranges = self.paint_highlights(layout, cx);
2645 self.paint_lines(&invisible_display_ranges, layout, cx);
2646 self.paint_redactions(layout, cx);
2647 self.paint_cursors(layout, cx);
2648 self.paint_inline_blame(layout, cx);
2649 },
2650 )
2651 }
2652
2653 fn paint_highlights(
2654 &mut self,
2655 layout: &mut EditorLayout,
2656 cx: &mut WindowContext,
2657 ) -> SmallVec<[Range<DisplayPoint>; 32]> {
2658 cx.paint_layer(layout.text_hitbox.bounds, |cx| {
2659 let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
2660 let line_end_overshoot = 0.15 * layout.position_map.line_height;
2661 for (range, color) in &layout.highlighted_ranges {
2662 self.paint_highlighted_range(
2663 range.clone(),
2664 *color,
2665 Pixels::ZERO,
2666 line_end_overshoot,
2667 layout,
2668 cx,
2669 );
2670 }
2671
2672 let corner_radius = 0.15 * layout.position_map.line_height;
2673
2674 for (player_color, selections) in &layout.selections {
2675 for selection in selections.into_iter() {
2676 self.paint_highlighted_range(
2677 selection.range.clone(),
2678 player_color.selection,
2679 corner_radius,
2680 corner_radius * 2.,
2681 layout,
2682 cx,
2683 );
2684
2685 if selection.is_local && !selection.range.is_empty() {
2686 invisible_display_ranges.push(selection.range.clone());
2687 }
2688 }
2689 }
2690 invisible_display_ranges
2691 })
2692 }
2693
2694 fn paint_lines(
2695 &mut self,
2696 invisible_display_ranges: &[Range<DisplayPoint>],
2697 layout: &EditorLayout,
2698 cx: &mut WindowContext,
2699 ) {
2700 let whitespace_setting = self
2701 .editor
2702 .read(cx)
2703 .buffer
2704 .read(cx)
2705 .settings_at(0, cx)
2706 .show_whitespaces;
2707
2708 for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
2709 let row = DisplayRow(layout.visible_display_row_range.start.0 + ix as u32);
2710 line_with_invisibles.draw(
2711 layout,
2712 row,
2713 layout.content_origin,
2714 whitespace_setting,
2715 invisible_display_ranges,
2716 cx,
2717 )
2718 }
2719 }
2720
2721 fn paint_redactions(&mut self, layout: &EditorLayout, cx: &mut WindowContext) {
2722 if layout.redacted_ranges.is_empty() {
2723 return;
2724 }
2725
2726 let line_end_overshoot = layout.line_end_overshoot();
2727
2728 // A softer than perfect black
2729 let redaction_color = gpui::rgb(0x0e1111);
2730
2731 cx.paint_layer(layout.text_hitbox.bounds, |cx| {
2732 for range in layout.redacted_ranges.iter() {
2733 self.paint_highlighted_range(
2734 range.clone(),
2735 redaction_color.into(),
2736 Pixels::ZERO,
2737 line_end_overshoot,
2738 layout,
2739 cx,
2740 );
2741 }
2742 });
2743 }
2744
2745 fn paint_cursors(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
2746 for cursor in &mut layout.visible_cursors {
2747 cursor.paint(layout.content_origin, cx);
2748 }
2749 }
2750
2751 fn paint_scrollbar(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
2752 let Some(scrollbar_layout) = layout.scrollbar_layout.as_ref() else {
2753 return;
2754 };
2755
2756 let thumb_bounds = scrollbar_layout.thumb_bounds();
2757 if scrollbar_layout.visible {
2758 cx.paint_layer(scrollbar_layout.hitbox.bounds, |cx| {
2759 cx.paint_quad(quad(
2760 scrollbar_layout.hitbox.bounds,
2761 Corners::default(),
2762 cx.theme().colors().scrollbar_track_background,
2763 Edges {
2764 top: Pixels::ZERO,
2765 right: Pixels::ZERO,
2766 bottom: Pixels::ZERO,
2767 left: ScrollbarLayout::BORDER_WIDTH,
2768 },
2769 cx.theme().colors().scrollbar_track_border,
2770 ));
2771
2772 let fast_markers =
2773 self.collect_fast_scrollbar_markers(layout, scrollbar_layout, cx);
2774 // Refresh slow scrollbar markers in the background. Below, we paint whatever markers have already been computed.
2775 self.refresh_slow_scrollbar_markers(layout, scrollbar_layout, cx);
2776
2777 let markers = self.editor.read(cx).scrollbar_marker_state.markers.clone();
2778 for marker in markers.iter().chain(&fast_markers) {
2779 let mut marker = marker.clone();
2780 marker.bounds.origin += scrollbar_layout.hitbox.origin;
2781 cx.paint_quad(marker);
2782 }
2783
2784 cx.paint_quad(quad(
2785 thumb_bounds,
2786 Corners::default(),
2787 cx.theme().colors().scrollbar_thumb_background,
2788 Edges {
2789 top: Pixels::ZERO,
2790 right: Pixels::ZERO,
2791 bottom: Pixels::ZERO,
2792 left: ScrollbarLayout::BORDER_WIDTH,
2793 },
2794 cx.theme().colors().scrollbar_thumb_border,
2795 ));
2796 });
2797 }
2798
2799 cx.set_cursor_style(CursorStyle::Arrow, &scrollbar_layout.hitbox);
2800
2801 let row_height = scrollbar_layout.row_height;
2802 let row_range = scrollbar_layout.visible_row_range.clone();
2803
2804 cx.on_mouse_event({
2805 let editor = self.editor.clone();
2806 let hitbox = scrollbar_layout.hitbox.clone();
2807 let mut mouse_position = cx.mouse_position();
2808 move |event: &MouseMoveEvent, phase, cx| {
2809 if phase == DispatchPhase::Capture {
2810 return;
2811 }
2812
2813 editor.update(cx, |editor, cx| {
2814 if event.pressed_button == Some(MouseButton::Left)
2815 && editor.scroll_manager.is_dragging_scrollbar()
2816 {
2817 let y = mouse_position.y;
2818 let new_y = event.position.y;
2819 if (hitbox.top()..hitbox.bottom()).contains(&y) {
2820 let mut position = editor.scroll_position(cx);
2821 position.y += (new_y - y) / row_height;
2822 if position.y < 0.0 {
2823 position.y = 0.0;
2824 }
2825 editor.set_scroll_position(position, cx);
2826 }
2827
2828 cx.stop_propagation();
2829 } else {
2830 editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
2831 if hitbox.is_hovered(cx) {
2832 editor.scroll_manager.show_scrollbar(cx);
2833 }
2834 }
2835 mouse_position = event.position;
2836 })
2837 }
2838 });
2839
2840 if self.editor.read(cx).scroll_manager.is_dragging_scrollbar() {
2841 cx.on_mouse_event({
2842 let editor = self.editor.clone();
2843 move |_: &MouseUpEvent, phase, cx| {
2844 if phase == DispatchPhase::Capture {
2845 return;
2846 }
2847
2848 editor.update(cx, |editor, cx| {
2849 editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
2850 cx.stop_propagation();
2851 });
2852 }
2853 });
2854 } else {
2855 cx.on_mouse_event({
2856 let editor = self.editor.clone();
2857 let hitbox = scrollbar_layout.hitbox.clone();
2858 move |event: &MouseDownEvent, phase, cx| {
2859 if phase == DispatchPhase::Capture || !hitbox.is_hovered(cx) {
2860 return;
2861 }
2862
2863 editor.update(cx, |editor, cx| {
2864 editor.scroll_manager.set_is_dragging_scrollbar(true, cx);
2865
2866 let y = event.position.y;
2867 if y < thumb_bounds.top() || thumb_bounds.bottom() < y {
2868 let center_row = ((y - hitbox.top()) / row_height).round() as u32;
2869 let top_row = center_row
2870 .saturating_sub((row_range.end - row_range.start) as u32 / 2);
2871 let mut position = editor.scroll_position(cx);
2872 position.y = top_row as f32;
2873 editor.set_scroll_position(position, cx);
2874 } else {
2875 editor.scroll_manager.show_scrollbar(cx);
2876 }
2877
2878 cx.stop_propagation();
2879 });
2880 }
2881 });
2882 }
2883 }
2884
2885 fn collect_fast_scrollbar_markers(
2886 &self,
2887 layout: &EditorLayout,
2888 scrollbar_layout: &ScrollbarLayout,
2889 cx: &mut WindowContext,
2890 ) -> Vec<PaintQuad> {
2891 const LIMIT: usize = 100;
2892 if !EditorSettings::get_global(cx).scrollbar.cursors || layout.cursors.len() > LIMIT {
2893 return vec![];
2894 }
2895 let cursor_ranges = layout
2896 .cursors
2897 .iter()
2898 .map(|(point, color)| ColoredRange {
2899 start: point.row(),
2900 end: point.row(),
2901 color: *color,
2902 })
2903 .collect_vec();
2904 scrollbar_layout.marker_quads_for_ranges(cursor_ranges, None)
2905 }
2906
2907 fn refresh_slow_scrollbar_markers(
2908 &self,
2909 layout: &EditorLayout,
2910 scrollbar_layout: &ScrollbarLayout,
2911 cx: &mut WindowContext,
2912 ) {
2913 self.editor.update(cx, |editor, cx| {
2914 if !editor.is_singleton(cx)
2915 || !editor
2916 .scrollbar_marker_state
2917 .should_refresh(scrollbar_layout.hitbox.size)
2918 {
2919 return;
2920 }
2921
2922 let scrollbar_layout = scrollbar_layout.clone();
2923 let background_highlights = editor.background_highlights.clone();
2924 let snapshot = layout.position_map.snapshot.clone();
2925 let theme = cx.theme().clone();
2926 let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
2927
2928 editor.scrollbar_marker_state.dirty = false;
2929 editor.scrollbar_marker_state.pending_refresh =
2930 Some(cx.spawn(|editor, mut cx| async move {
2931 let scrollbar_size = scrollbar_layout.hitbox.size;
2932 let scrollbar_markers = cx
2933 .background_executor()
2934 .spawn(async move {
2935 let max_point = snapshot.display_snapshot.buffer_snapshot.max_point();
2936 let mut marker_quads = Vec::new();
2937 if scrollbar_settings.git_diff {
2938 let marker_row_ranges = snapshot
2939 .buffer_snapshot
2940 .git_diff_hunks_in_range(
2941 MultiBufferRow::MIN..MultiBufferRow::MAX,
2942 )
2943 .map(|hunk| {
2944 let start_display_row =
2945 MultiBufferPoint::new(hunk.associated_range.start.0, 0)
2946 .to_display_point(&snapshot.display_snapshot)
2947 .row();
2948 let mut end_display_row =
2949 MultiBufferPoint::new(hunk.associated_range.end.0, 0)
2950 .to_display_point(&snapshot.display_snapshot)
2951 .row();
2952 if end_display_row != start_display_row {
2953 end_display_row.0 -= 1;
2954 }
2955 let color = match hunk_status(&hunk) {
2956 DiffHunkStatus::Added => theme.status().created,
2957 DiffHunkStatus::Modified => theme.status().modified,
2958 DiffHunkStatus::Removed => theme.status().deleted,
2959 };
2960 ColoredRange {
2961 start: start_display_row,
2962 end: end_display_row,
2963 color,
2964 }
2965 });
2966
2967 marker_quads.extend(
2968 scrollbar_layout
2969 .marker_quads_for_ranges(marker_row_ranges, Some(0)),
2970 );
2971 }
2972
2973 for (background_highlight_id, (_, background_ranges)) in
2974 background_highlights.iter()
2975 {
2976 let is_search_highlights = *background_highlight_id
2977 == TypeId::of::<BufferSearchHighlights>();
2978 let is_symbol_occurrences = *background_highlight_id
2979 == TypeId::of::<DocumentHighlightRead>()
2980 || *background_highlight_id
2981 == TypeId::of::<DocumentHighlightWrite>();
2982 if (is_search_highlights && scrollbar_settings.search_results)
2983 || (is_symbol_occurrences && scrollbar_settings.selected_symbol)
2984 {
2985 let mut color = theme.status().info;
2986 if is_symbol_occurrences {
2987 color.fade_out(0.5);
2988 }
2989 let marker_row_ranges =
2990 background_ranges.into_iter().map(|range| {
2991 let display_start = range
2992 .start
2993 .to_display_point(&snapshot.display_snapshot);
2994 let display_end = range
2995 .end
2996 .to_display_point(&snapshot.display_snapshot);
2997 ColoredRange {
2998 start: display_start.row(),
2999 end: display_end.row(),
3000 color,
3001 }
3002 });
3003 marker_quads.extend(
3004 scrollbar_layout
3005 .marker_quads_for_ranges(marker_row_ranges, Some(1)),
3006 );
3007 }
3008 }
3009
3010 if scrollbar_settings.diagnostics {
3011 let diagnostics = snapshot
3012 .buffer_snapshot
3013 .diagnostics_in_range::<_, Point>(
3014 Point::zero()..max_point,
3015 false,
3016 )
3017 // We want to sort by severity, in order to paint the most severe diagnostics last.
3018 .sorted_by_key(|diagnostic| {
3019 std::cmp::Reverse(diagnostic.diagnostic.severity)
3020 });
3021
3022 let marker_row_ranges = diagnostics.into_iter().map(|diagnostic| {
3023 let start_display = diagnostic
3024 .range
3025 .start
3026 .to_display_point(&snapshot.display_snapshot);
3027 let end_display = diagnostic
3028 .range
3029 .end
3030 .to_display_point(&snapshot.display_snapshot);
3031 let color = match diagnostic.diagnostic.severity {
3032 DiagnosticSeverity::ERROR => theme.status().error,
3033 DiagnosticSeverity::WARNING => theme.status().warning,
3034 DiagnosticSeverity::INFORMATION => theme.status().info,
3035 _ => theme.status().hint,
3036 };
3037 ColoredRange {
3038 start: start_display.row(),
3039 end: end_display.row(),
3040 color,
3041 }
3042 });
3043 marker_quads.extend(
3044 scrollbar_layout
3045 .marker_quads_for_ranges(marker_row_ranges, Some(2)),
3046 );
3047 }
3048
3049 Arc::from(marker_quads)
3050 })
3051 .await;
3052
3053 editor.update(&mut cx, |editor, cx| {
3054 editor.scrollbar_marker_state.markers = scrollbar_markers;
3055 editor.scrollbar_marker_state.scrollbar_size = scrollbar_size;
3056 editor.scrollbar_marker_state.pending_refresh = None;
3057 cx.notify();
3058 })?;
3059
3060 Ok(())
3061 }));
3062 });
3063 }
3064
3065 #[allow(clippy::too_many_arguments)]
3066 fn paint_highlighted_range(
3067 &self,
3068 range: Range<DisplayPoint>,
3069 color: Hsla,
3070 corner_radius: Pixels,
3071 line_end_overshoot: Pixels,
3072 layout: &EditorLayout,
3073 cx: &mut WindowContext,
3074 ) {
3075 let start_row = layout.visible_display_row_range.start;
3076 let end_row = layout.visible_display_row_range.end;
3077 if range.start != range.end {
3078 let row_range = if range.end.column() == 0 {
3079 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
3080 } else {
3081 cmp::max(range.start.row(), start_row)
3082 ..cmp::min(range.end.row().next_row(), end_row)
3083 };
3084
3085 let highlighted_range = HighlightedRange {
3086 color,
3087 line_height: layout.position_map.line_height,
3088 corner_radius,
3089 start_y: layout.content_origin.y
3090 + row_range.start.as_f32() * layout.position_map.line_height
3091 - layout.position_map.scroll_pixel_position.y,
3092 lines: row_range
3093 .iter_rows()
3094 .map(|row| {
3095 let line_layout =
3096 &layout.position_map.line_layouts[row.minus(start_row) as usize].line;
3097 HighlightedRangeLine {
3098 start_x: if row == range.start.row() {
3099 layout.content_origin.x
3100 + line_layout.x_for_index(range.start.column() as usize)
3101 - layout.position_map.scroll_pixel_position.x
3102 } else {
3103 layout.content_origin.x
3104 - layout.position_map.scroll_pixel_position.x
3105 },
3106 end_x: if row == range.end.row() {
3107 layout.content_origin.x
3108 + line_layout.x_for_index(range.end.column() as usize)
3109 - layout.position_map.scroll_pixel_position.x
3110 } else {
3111 layout.content_origin.x + line_layout.width + line_end_overshoot
3112 - layout.position_map.scroll_pixel_position.x
3113 },
3114 }
3115 })
3116 .collect(),
3117 };
3118
3119 highlighted_range.paint(layout.text_hitbox.bounds, cx);
3120 }
3121 }
3122
3123 fn paint_folds(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
3124 if layout.folds.is_empty() {
3125 return;
3126 }
3127
3128 cx.paint_layer(layout.text_hitbox.bounds, |cx| {
3129 let fold_corner_radius = 0.15 * layout.position_map.line_height;
3130 for mut fold in mem::take(&mut layout.folds) {
3131 fold.hover_element.paint(cx);
3132
3133 let hover_element = fold.hover_element.downcast_mut::<Stateful<Div>>().unwrap();
3134 let fold_background = if hover_element.interactivity().active.unwrap() {
3135 cx.theme().colors().ghost_element_active
3136 } else if hover_element.interactivity().hovered.unwrap() {
3137 cx.theme().colors().ghost_element_hover
3138 } else {
3139 cx.theme().colors().ghost_element_background
3140 };
3141
3142 self.paint_highlighted_range(
3143 fold.display_range.clone(),
3144 fold_background,
3145 fold_corner_radius,
3146 fold_corner_radius * 2.,
3147 layout,
3148 cx,
3149 );
3150 }
3151 })
3152 }
3153
3154 fn paint_inline_blame(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
3155 if let Some(mut inline_blame) = layout.inline_blame.take() {
3156 cx.paint_layer(layout.text_hitbox.bounds, |cx| {
3157 inline_blame.paint(cx);
3158 })
3159 }
3160 }
3161
3162 fn paint_blocks(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
3163 for mut block in layout.blocks.drain(..) {
3164 block.element.paint(cx);
3165 }
3166 }
3167
3168 fn paint_mouse_context_menu(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
3169 if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() {
3170 mouse_context_menu.paint(cx);
3171 }
3172 }
3173
3174 fn paint_scroll_wheel_listener(&mut self, layout: &EditorLayout, cx: &mut WindowContext) {
3175 cx.on_mouse_event({
3176 let position_map = layout.position_map.clone();
3177 let editor = self.editor.clone();
3178 let hitbox = layout.hitbox.clone();
3179 let mut delta = ScrollDelta::default();
3180
3181 // Set a minimum scroll_sensitivity of 0.01 to make sure the user doesn't
3182 // accidentally turn off their scrolling.
3183 let scroll_sensitivity = EditorSettings::get_global(cx).scroll_sensitivity.max(0.01);
3184
3185 move |event: &ScrollWheelEvent, phase, cx| {
3186 if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
3187 delta = delta.coalesce(event.delta);
3188 editor.update(cx, |editor, cx| {
3189 let position_map: &PositionMap = &position_map;
3190
3191 let line_height = position_map.line_height;
3192 let max_glyph_width = position_map.em_width;
3193 let (delta, axis) = match delta {
3194 gpui::ScrollDelta::Pixels(mut pixels) => {
3195 //Trackpad
3196 let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
3197 (pixels, axis)
3198 }
3199
3200 gpui::ScrollDelta::Lines(lines) => {
3201 //Not trackpad
3202 let pixels =
3203 point(lines.x * max_glyph_width, lines.y * line_height);
3204 (pixels, None)
3205 }
3206 };
3207
3208 let current_scroll_position = position_map.snapshot.scroll_position();
3209 let x = (current_scroll_position.x * max_glyph_width
3210 - (delta.x * scroll_sensitivity))
3211 / max_glyph_width;
3212 let y = (current_scroll_position.y * line_height
3213 - (delta.y * scroll_sensitivity))
3214 / line_height;
3215 let mut scroll_position =
3216 point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
3217 let forbid_vertical_scroll = editor.scroll_manager.forbid_vertical_scroll();
3218 if forbid_vertical_scroll {
3219 scroll_position.y = current_scroll_position.y;
3220 if scroll_position == current_scroll_position {
3221 return;
3222 }
3223 }
3224 editor.scroll(scroll_position, axis, cx);
3225 cx.stop_propagation();
3226 });
3227 }
3228 }
3229 });
3230 }
3231
3232 fn paint_mouse_listeners(
3233 &mut self,
3234 layout: &EditorLayout,
3235 hovered_hunk: Option<HunkToExpand>,
3236 cx: &mut WindowContext,
3237 ) {
3238 self.paint_scroll_wheel_listener(layout, cx);
3239
3240 cx.on_mouse_event({
3241 let position_map = layout.position_map.clone();
3242 let editor = self.editor.clone();
3243 let text_hitbox = layout.text_hitbox.clone();
3244 let gutter_hitbox = layout.gutter_hitbox.clone();
3245
3246 move |event: &MouseDownEvent, phase, cx| {
3247 if phase == DispatchPhase::Bubble {
3248 match event.button {
3249 MouseButton::Left => editor.update(cx, |editor, cx| {
3250 Self::mouse_left_down(
3251 editor,
3252 event,
3253 hovered_hunk.as_ref(),
3254 &position_map,
3255 &text_hitbox,
3256 &gutter_hitbox,
3257 cx,
3258 );
3259 }),
3260 MouseButton::Right => editor.update(cx, |editor, cx| {
3261 Self::mouse_right_down(editor, event, &position_map, &text_hitbox, cx);
3262 }),
3263 MouseButton::Middle => editor.update(cx, |editor, cx| {
3264 Self::mouse_middle_down(editor, event, &position_map, &text_hitbox, cx);
3265 }),
3266 _ => {}
3267 };
3268 }
3269 }
3270 });
3271
3272 cx.on_mouse_event({
3273 let editor = self.editor.clone();
3274 let position_map = layout.position_map.clone();
3275 let text_hitbox = layout.text_hitbox.clone();
3276
3277 move |event: &MouseUpEvent, phase, cx| {
3278 if phase == DispatchPhase::Bubble {
3279 editor.update(cx, |editor, cx| {
3280 Self::mouse_up(editor, event, &position_map, &text_hitbox, cx)
3281 });
3282 }
3283 }
3284 });
3285 cx.on_mouse_event({
3286 let position_map = layout.position_map.clone();
3287 let editor = self.editor.clone();
3288 let text_hitbox = layout.text_hitbox.clone();
3289 let gutter_hitbox = layout.gutter_hitbox.clone();
3290
3291 move |event: &MouseMoveEvent, phase, cx| {
3292 if phase == DispatchPhase::Bubble {
3293 editor.update(cx, |editor, cx| {
3294 if event.pressed_button == Some(MouseButton::Left)
3295 || event.pressed_button == Some(MouseButton::Middle)
3296 {
3297 Self::mouse_dragged(
3298 editor,
3299 event,
3300 &position_map,
3301 text_hitbox.bounds,
3302 cx,
3303 )
3304 }
3305
3306 Self::mouse_moved(
3307 editor,
3308 event,
3309 &position_map,
3310 &text_hitbox,
3311 &gutter_hitbox,
3312 cx,
3313 )
3314 });
3315 }
3316 }
3317 });
3318 }
3319
3320 fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
3321 bounds.upper_right().x - self.style.scrollbar_width
3322 }
3323
3324 fn column_pixels(&self, column: usize, cx: &WindowContext) -> Pixels {
3325 let style = &self.style;
3326 let font_size = style.text.font_size.to_pixels(cx.rem_size());
3327 let layout = cx
3328 .text_system()
3329 .shape_line(
3330 SharedString::from(" ".repeat(column)),
3331 font_size,
3332 &[TextRun {
3333 len: column,
3334 font: style.text.font(),
3335 color: Hsla::default(),
3336 background_color: None,
3337 underline: None,
3338 strikethrough: None,
3339 }],
3340 )
3341 .unwrap();
3342
3343 layout.width
3344 }
3345
3346 fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &WindowContext) -> Pixels {
3347 let digit_count = snapshot
3348 .max_buffer_row()
3349 .next_row()
3350 .as_f32()
3351 .log10()
3352 .floor() as usize
3353 + 1;
3354 self.column_pixels(digit_count, cx)
3355 }
3356}
3357
3358fn prepaint_gutter_button(
3359 button: IconButton,
3360 row: DisplayRow,
3361 line_height: Pixels,
3362 gutter_dimensions: &GutterDimensions,
3363 scroll_pixel_position: gpui::Point<Pixels>,
3364 gutter_hitbox: &Hitbox,
3365 cx: &mut WindowContext<'_>,
3366) -> AnyElement {
3367 let mut button = button.into_any_element();
3368 let available_space = size(
3369 AvailableSpace::MinContent,
3370 AvailableSpace::Definite(line_height),
3371 );
3372 let indicator_size = button.layout_as_root(available_space, cx);
3373
3374 let blame_width = gutter_dimensions
3375 .git_blame_entries_width
3376 .unwrap_or(Pixels::ZERO);
3377
3378 let mut x = blame_width;
3379 let available_width = gutter_dimensions.margin + gutter_dimensions.left_padding
3380 - indicator_size.width
3381 - blame_width;
3382 x += available_width / 2.;
3383
3384 let mut y = row.as_f32() * line_height - scroll_pixel_position.y;
3385 y += (line_height - indicator_size.height) / 2.;
3386
3387 button.prepaint_as_root(gutter_hitbox.origin + point(x, y), available_space, cx);
3388 button
3389}
3390
3391fn render_inline_blame_entry(
3392 blame: &gpui::Model<GitBlame>,
3393 blame_entry: BlameEntry,
3394 style: &EditorStyle,
3395 workspace: Option<WeakView<Workspace>>,
3396 cx: &mut WindowContext<'_>,
3397) -> AnyElement {
3398 let relative_timestamp = blame_entry_relative_timestamp(&blame_entry, cx);
3399
3400 let author = blame_entry.author.as_deref().unwrap_or_default();
3401 let text = format!("{}, {}", author, relative_timestamp);
3402
3403 let details = blame.read(cx).details_for_entry(&blame_entry);
3404
3405 let tooltip = cx.new_view(|_| BlameEntryTooltip::new(blame_entry, details, style, workspace));
3406
3407 h_flex()
3408 .id("inline-blame")
3409 .w_full()
3410 .font_family(style.text.font().family)
3411 .text_color(cx.theme().status().hint)
3412 .line_height(style.text.line_height)
3413 .child(Icon::new(IconName::FileGit).color(Color::Hint))
3414 .child(text)
3415 .gap_2()
3416 .hoverable_tooltip(move |_| tooltip.clone().into())
3417 .into_any()
3418}
3419
3420fn render_blame_entry(
3421 ix: usize,
3422 blame: &gpui::Model<GitBlame>,
3423 blame_entry: BlameEntry,
3424 style: &EditorStyle,
3425 last_used_color: &mut Option<(PlayerColor, Oid)>,
3426 editor: View<Editor>,
3427 cx: &mut WindowContext<'_>,
3428) -> AnyElement {
3429 let mut sha_color = cx
3430 .theme()
3431 .players()
3432 .color_for_participant(blame_entry.sha.into());
3433 // If the last color we used is the same as the one we get for this line, but
3434 // the commit SHAs are different, then we try again to get a different color.
3435 match *last_used_color {
3436 Some((color, sha)) if sha != blame_entry.sha && color.cursor == sha_color.cursor => {
3437 let index: u32 = blame_entry.sha.into();
3438 sha_color = cx.theme().players().color_for_participant(index + 1);
3439 }
3440 _ => {}
3441 };
3442 last_used_color.replace((sha_color, blame_entry.sha));
3443
3444 let relative_timestamp = blame_entry_relative_timestamp(&blame_entry, cx);
3445
3446 let short_commit_id = blame_entry.sha.display_short();
3447
3448 let author_name = blame_entry.author.as_deref().unwrap_or("<no name>");
3449 let name = util::truncate_and_trailoff(author_name, 20);
3450
3451 let details = blame.read(cx).details_for_entry(&blame_entry);
3452
3453 let workspace = editor.read(cx).workspace.as_ref().map(|(w, _)| w.clone());
3454
3455 let tooltip = cx.new_view(|_| {
3456 BlameEntryTooltip::new(blame_entry.clone(), details.clone(), style, workspace)
3457 });
3458
3459 h_flex()
3460 .w_full()
3461 .font_family(style.text.font().family)
3462 .line_height(style.text.line_height)
3463 .id(("blame", ix))
3464 .children([
3465 div()
3466 .text_color(sha_color.cursor)
3467 .child(short_commit_id)
3468 .mr_2(),
3469 div()
3470 .w_full()
3471 .h_flex()
3472 .justify_between()
3473 .text_color(cx.theme().status().hint)
3474 .child(name)
3475 .child(relative_timestamp),
3476 ])
3477 .on_mouse_down(MouseButton::Right, {
3478 let blame_entry = blame_entry.clone();
3479 let details = details.clone();
3480 move |event, cx| {
3481 deploy_blame_entry_context_menu(
3482 &blame_entry,
3483 details.as_ref(),
3484 editor.clone(),
3485 event.position,
3486 cx,
3487 );
3488 }
3489 })
3490 .hover(|style| style.bg(cx.theme().colors().element_hover))
3491 .when_some(
3492 details.and_then(|details| details.permalink),
3493 |this, url| {
3494 let url = url.clone();
3495 this.cursor_pointer().on_click(move |_, cx| {
3496 cx.stop_propagation();
3497 cx.open_url(url.as_str())
3498 })
3499 },
3500 )
3501 .hoverable_tooltip(move |_| tooltip.clone().into())
3502 .into_any()
3503}
3504
3505fn deploy_blame_entry_context_menu(
3506 blame_entry: &BlameEntry,
3507 details: Option<&CommitDetails>,
3508 editor: View<Editor>,
3509 position: gpui::Point<Pixels>,
3510 cx: &mut WindowContext<'_>,
3511) {
3512 let context_menu = ContextMenu::build(cx, move |this, _| {
3513 let sha = format!("{}", blame_entry.sha);
3514 this.entry("Copy commit SHA", None, move |cx| {
3515 cx.write_to_clipboard(ClipboardItem::new(sha.clone()));
3516 })
3517 .when_some(
3518 details.and_then(|details| details.permalink.clone()),
3519 |this, url| this.entry("Open permalink", None, move |cx| cx.open_url(url.as_str())),
3520 )
3521 });
3522
3523 editor.update(cx, move |editor, cx| {
3524 editor.mouse_context_menu = Some(MouseContextMenu::new(position, context_menu, cx));
3525 cx.notify();
3526 });
3527}
3528
3529#[derive(Debug)]
3530pub(crate) struct LineWithInvisibles {
3531 pub line: ShapedLine,
3532 invisibles: Vec<Invisible>,
3533}
3534
3535impl LineWithInvisibles {
3536 fn from_chunks<'a>(
3537 chunks: impl Iterator<Item = HighlightedChunk<'a>>,
3538 text_style: &TextStyle,
3539 max_line_len: usize,
3540 max_line_count: usize,
3541 line_number_layouts: &[Option<ShapedLine>],
3542 editor_mode: EditorMode,
3543 cx: &WindowContext,
3544 ) -> Vec<Self> {
3545 let mut layouts = Vec::with_capacity(max_line_count);
3546 let mut line = String::new();
3547 let mut invisibles = Vec::new();
3548 let mut styles = Vec::new();
3549 let mut non_whitespace_added = false;
3550 let mut row = 0;
3551 let mut line_exceeded_max_len = false;
3552 let font_size = text_style.font_size.to_pixels(cx.rem_size());
3553
3554 for highlighted_chunk in chunks.chain([HighlightedChunk {
3555 chunk: "\n",
3556 style: None,
3557 is_tab: false,
3558 }]) {
3559 for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() {
3560 if ix > 0 {
3561 let shaped_line = cx
3562 .text_system()
3563 .shape_line(line.clone().into(), font_size, &styles)
3564 .unwrap();
3565 layouts.push(Self {
3566 line: shaped_line,
3567 invisibles: std::mem::take(&mut invisibles),
3568 });
3569
3570 line.clear();
3571 styles.clear();
3572 row += 1;
3573 line_exceeded_max_len = false;
3574 non_whitespace_added = false;
3575 if row == max_line_count {
3576 return layouts;
3577 }
3578 }
3579
3580 if !line_chunk.is_empty() && !line_exceeded_max_len {
3581 let text_style = if let Some(style) = highlighted_chunk.style {
3582 Cow::Owned(text_style.clone().highlight(style))
3583 } else {
3584 Cow::Borrowed(text_style)
3585 };
3586
3587 if line.len() + line_chunk.len() > max_line_len {
3588 let mut chunk_len = max_line_len - line.len();
3589 while !line_chunk.is_char_boundary(chunk_len) {
3590 chunk_len -= 1;
3591 }
3592 line_chunk = &line_chunk[..chunk_len];
3593 line_exceeded_max_len = true;
3594 }
3595
3596 styles.push(TextRun {
3597 len: line_chunk.len(),
3598 font: text_style.font(),
3599 color: text_style.color,
3600 background_color: text_style.background_color,
3601 underline: text_style.underline,
3602 strikethrough: text_style.strikethrough,
3603 });
3604
3605 if editor_mode == EditorMode::Full {
3606 // Line wrap pads its contents with fake whitespaces,
3607 // avoid printing them
3608 let inside_wrapped_string = line_number_layouts
3609 .get(row)
3610 .and_then(|layout| layout.as_ref())
3611 .is_none();
3612 if highlighted_chunk.is_tab {
3613 if non_whitespace_added || !inside_wrapped_string {
3614 invisibles.push(Invisible::Tab {
3615 line_start_offset: line.len(),
3616 });
3617 }
3618 } else {
3619 invisibles.extend(
3620 line_chunk
3621 .chars()
3622 .enumerate()
3623 .filter(|(_, line_char)| {
3624 let is_whitespace = line_char.is_whitespace();
3625 non_whitespace_added |= !is_whitespace;
3626 is_whitespace
3627 && (non_whitespace_added || !inside_wrapped_string)
3628 })
3629 .map(|(whitespace_index, _)| Invisible::Whitespace {
3630 line_offset: line.len() + whitespace_index,
3631 }),
3632 )
3633 }
3634 }
3635
3636 line.push_str(line_chunk);
3637 }
3638 }
3639 }
3640
3641 layouts
3642 }
3643
3644 fn draw(
3645 &self,
3646 layout: &EditorLayout,
3647 row: DisplayRow,
3648 content_origin: gpui::Point<Pixels>,
3649 whitespace_setting: ShowWhitespaceSetting,
3650 selection_ranges: &[Range<DisplayPoint>],
3651 cx: &mut WindowContext,
3652 ) {
3653 let line_height = layout.position_map.line_height;
3654 let line_y = line_height
3655 * (row.as_f32() - layout.position_map.scroll_pixel_position.y / line_height);
3656
3657 let line_origin =
3658 content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y);
3659 self.line.paint(line_origin, line_height, cx).log_err();
3660
3661 self.draw_invisibles(
3662 &selection_ranges,
3663 layout,
3664 content_origin,
3665 line_y,
3666 row,
3667 line_height,
3668 whitespace_setting,
3669 cx,
3670 );
3671 }
3672
3673 #[allow(clippy::too_many_arguments)]
3674 fn draw_invisibles(
3675 &self,
3676 selection_ranges: &[Range<DisplayPoint>],
3677 layout: &EditorLayout,
3678 content_origin: gpui::Point<Pixels>,
3679 line_y: Pixels,
3680 row: DisplayRow,
3681 line_height: Pixels,
3682 whitespace_setting: ShowWhitespaceSetting,
3683 cx: &mut WindowContext,
3684 ) {
3685 let allowed_invisibles_regions = match whitespace_setting {
3686 ShowWhitespaceSetting::None => return,
3687 ShowWhitespaceSetting::Selection => Some(selection_ranges),
3688 ShowWhitespaceSetting::All => None,
3689 };
3690
3691 for invisible in &self.invisibles {
3692 let (&token_offset, invisible_symbol) = match invisible {
3693 Invisible::Tab { line_start_offset } => (line_start_offset, &layout.tab_invisible),
3694 Invisible::Whitespace { line_offset } => (line_offset, &layout.space_invisible),
3695 };
3696
3697 let x_offset = self.line.x_for_index(token_offset);
3698 let invisible_offset =
3699 (layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
3700 let origin = content_origin
3701 + gpui::point(
3702 x_offset + invisible_offset - layout.position_map.scroll_pixel_position.x,
3703 line_y,
3704 );
3705
3706 if let Some(allowed_regions) = allowed_invisibles_regions {
3707 let invisible_point = DisplayPoint::new(row, token_offset as u32);
3708 if !allowed_regions
3709 .iter()
3710 .any(|region| region.start <= invisible_point && invisible_point < region.end)
3711 {
3712 continue;
3713 }
3714 }
3715 invisible_symbol.paint(origin, line_height, cx).log_err();
3716 }
3717 }
3718}
3719
3720#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3721enum Invisible {
3722 Tab { line_start_offset: usize },
3723 Whitespace { line_offset: usize },
3724}
3725
3726impl EditorElement {
3727 /// Returns the rem size to use when rendering the [`EditorElement`].
3728 ///
3729 /// This allows UI elements to scale based on the `buffer_font_size`.
3730 fn rem_size(&self, cx: &WindowContext) -> Option<Pixels> {
3731 match self.editor.read(cx).mode {
3732 EditorMode::Full => {
3733 let buffer_font_size = self.style.text.font_size;
3734 match buffer_font_size {
3735 AbsoluteLength::Pixels(pixels) => {
3736 let rem_size_scale = {
3737 // Our default UI font size is 14px on a 16px base scale.
3738 // This means the default UI font size is 0.875rems.
3739 let default_font_size_scale = 14. / ui::BASE_REM_SIZE_IN_PX;
3740
3741 // We then determine the delta between a single rem and the default font
3742 // size scale.
3743 let default_font_size_delta = 1. - default_font_size_scale;
3744
3745 // Finally, we add this delta to 1rem to get the scale factor that
3746 // should be used to scale up the UI.
3747 1. + default_font_size_delta
3748 };
3749
3750 Some(pixels * rem_size_scale)
3751 }
3752 AbsoluteLength::Rems(rems) => {
3753 Some(rems.to_pixels(ui::BASE_REM_SIZE_IN_PX.into()))
3754 }
3755 }
3756 }
3757 // We currently use single-line and auto-height editors in UI contexts,
3758 // so we don't want to scale everything with the buffer font size, as it
3759 // ends up looking off.
3760 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => None,
3761 }
3762 }
3763}
3764
3765impl Element for EditorElement {
3766 type RequestLayoutState = ();
3767 type PrepaintState = EditorLayout;
3768
3769 fn id(&self) -> Option<ElementId> {
3770 None
3771 }
3772
3773 fn request_layout(
3774 &mut self,
3775 _: Option<&GlobalElementId>,
3776 cx: &mut WindowContext,
3777 ) -> (gpui::LayoutId, ()) {
3778 let rem_size = self.rem_size(cx);
3779 cx.with_rem_size(rem_size, |cx| {
3780 self.editor.update(cx, |editor, cx| {
3781 editor.set_style(self.style.clone(), cx);
3782
3783 let layout_id = match editor.mode {
3784 EditorMode::SingleLine => {
3785 let rem_size = cx.rem_size();
3786 let mut style = Style::default();
3787 style.size.width = relative(1.).into();
3788 style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
3789 cx.request_layout(style, None)
3790 }
3791 EditorMode::AutoHeight { max_lines } => {
3792 let editor_handle = cx.view().clone();
3793 let max_line_number_width =
3794 self.max_line_number_width(&editor.snapshot(cx), cx);
3795 cx.request_measured_layout(
3796 Style::default(),
3797 move |known_dimensions, available_space, cx| {
3798 editor_handle
3799 .update(cx, |editor, cx| {
3800 compute_auto_height_layout(
3801 editor,
3802 max_lines,
3803 max_line_number_width,
3804 known_dimensions,
3805 available_space.width,
3806 cx,
3807 )
3808 })
3809 .unwrap_or_default()
3810 },
3811 )
3812 }
3813 EditorMode::Full => {
3814 let mut style = Style::default();
3815 style.size.width = relative(1.).into();
3816 style.size.height = relative(1.).into();
3817 cx.request_layout(style, None)
3818 }
3819 };
3820
3821 (layout_id, ())
3822 })
3823 })
3824 }
3825
3826 fn prepaint(
3827 &mut self,
3828 _: Option<&GlobalElementId>,
3829 bounds: Bounds<Pixels>,
3830 _: &mut Self::RequestLayoutState,
3831 cx: &mut WindowContext,
3832 ) -> Self::PrepaintState {
3833 let text_style = TextStyleRefinement {
3834 font_size: Some(self.style.text.font_size),
3835 line_height: Some(self.style.text.line_height),
3836 ..Default::default()
3837 };
3838 cx.set_view_id(self.editor.entity_id());
3839
3840 let rem_size = self.rem_size(cx);
3841 cx.with_rem_size(rem_size, |cx| {
3842 cx.with_text_style(Some(text_style), |cx| {
3843 cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
3844 let mut snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
3845 let style = self.style.clone();
3846
3847 let font_id = cx.text_system().resolve_font(&style.text.font());
3848 let font_size = style.text.font_size.to_pixels(cx.rem_size());
3849 let line_height = style.text.line_height_in_pixels(cx.rem_size());
3850 let em_width = cx
3851 .text_system()
3852 .typographic_bounds(font_id, font_size, 'm')
3853 .unwrap()
3854 .size
3855 .width;
3856 let em_advance = cx
3857 .text_system()
3858 .advance(font_id, font_size, 'm')
3859 .unwrap()
3860 .width;
3861
3862 let gutter_dimensions = snapshot.gutter_dimensions(
3863 font_id,
3864 font_size,
3865 em_width,
3866 self.max_line_number_width(&snapshot, cx),
3867 cx,
3868 );
3869 let text_width = bounds.size.width - gutter_dimensions.width;
3870
3871 let right_margin = if snapshot.mode == EditorMode::Full {
3872 EditorElement::SCROLLBAR_WIDTH
3873 } else {
3874 px(0.)
3875 };
3876 let overscroll = size(em_width + right_margin, px(0.));
3877
3878 snapshot = self.editor.update(cx, |editor, cx| {
3879 editor.last_bounds = Some(bounds);
3880 editor.gutter_dimensions = gutter_dimensions;
3881 editor.set_visible_line_count(bounds.size.height / line_height, cx);
3882
3883 let editor_width =
3884 text_width - gutter_dimensions.margin - overscroll.width - em_width;
3885 let wrap_width = match editor.soft_wrap_mode(cx) {
3886 SoftWrap::None => None,
3887 SoftWrap::PreferLine => Some((MAX_LINE_LEN / 2) as f32 * em_advance),
3888 SoftWrap::EditorWidth => Some(editor_width),
3889 SoftWrap::Column(column) => {
3890 Some(editor_width.min(column as f32 * em_advance))
3891 }
3892 };
3893
3894 if editor.set_wrap_width(wrap_width, cx) {
3895 editor.snapshot(cx)
3896 } else {
3897 snapshot
3898 }
3899 });
3900
3901 let wrap_guides = self
3902 .editor
3903 .read(cx)
3904 .wrap_guides(cx)
3905 .iter()
3906 .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
3907 .collect::<SmallVec<[_; 2]>>();
3908
3909 let hitbox = cx.insert_hitbox(bounds, false);
3910 let gutter_hitbox = cx.insert_hitbox(
3911 Bounds {
3912 origin: bounds.origin,
3913 size: size(gutter_dimensions.width, bounds.size.height),
3914 },
3915 false,
3916 );
3917 let text_hitbox = cx.insert_hitbox(
3918 Bounds {
3919 origin: gutter_hitbox.upper_right(),
3920 size: size(text_width, bounds.size.height),
3921 },
3922 false,
3923 );
3924 // Offset the content_bounds from the text_bounds by the gutter margin (which
3925 // is roughly half a character wide) to make hit testing work more like how we want.
3926 let content_origin =
3927 text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
3928
3929 let mut autoscroll_containing_element = false;
3930 let mut autoscroll_horizontally = false;
3931 self.editor.update(cx, |editor, cx| {
3932 autoscroll_containing_element =
3933 editor.autoscroll_requested() || editor.has_pending_selection();
3934 autoscroll_horizontally =
3935 editor.autoscroll_vertically(bounds, line_height, cx);
3936 snapshot = editor.snapshot(cx);
3937 });
3938
3939 let mut scroll_position = snapshot.scroll_position();
3940 // The scroll position is a fractional point, the whole number of which represents
3941 // the top of the window in terms of display rows.
3942 let start_row = DisplayRow(scroll_position.y as u32);
3943 let height_in_lines = bounds.size.height / line_height;
3944 let max_row = snapshot.max_point().row();
3945 let end_row = cmp::min(
3946 (scroll_position.y + height_in_lines).ceil() as u32,
3947 max_row.next_row().0,
3948 );
3949 let end_row = DisplayRow(end_row);
3950
3951 let buffer_rows = snapshot
3952 .buffer_rows(start_row)
3953 .take((start_row..end_row).len())
3954 .collect::<Vec<_>>();
3955
3956 let start_anchor = if start_row == Default::default() {
3957 Anchor::min()
3958 } else {
3959 snapshot.buffer_snapshot.anchor_before(
3960 DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left),
3961 )
3962 };
3963 let end_anchor = if end_row > max_row {
3964 Anchor::max()
3965 } else {
3966 snapshot.buffer_snapshot.anchor_before(
3967 DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right),
3968 )
3969 };
3970
3971 let highlighted_rows = self
3972 .editor
3973 .update(cx, |editor, cx| editor.highlighted_display_rows(cx));
3974 let highlighted_ranges = self.editor.read(cx).background_highlights_in_range(
3975 start_anchor..end_anchor,
3976 &snapshot.display_snapshot,
3977 cx.theme().colors(),
3978 );
3979
3980 let redacted_ranges = self.editor.read(cx).redacted_ranges(
3981 start_anchor..end_anchor,
3982 &snapshot.display_snapshot,
3983 cx,
3984 );
3985
3986 let (selections, active_rows, newest_selection_head) = self.layout_selections(
3987 start_anchor,
3988 end_anchor,
3989 &snapshot,
3990 start_row,
3991 end_row,
3992 cx,
3993 );
3994
3995 let (line_numbers, fold_statuses) = self.layout_line_numbers(
3996 start_row..end_row,
3997 buffer_rows.clone().into_iter(),
3998 &active_rows,
3999 newest_selection_head,
4000 &snapshot,
4001 cx,
4002 );
4003
4004 let display_hunks = self.layout_git_gutters(
4005 line_height,
4006 &gutter_hitbox,
4007 start_row..end_row,
4008 &snapshot,
4009 cx,
4010 );
4011
4012 let mut max_visible_line_width = Pixels::ZERO;
4013 let line_layouts =
4014 self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
4015 for line_with_invisibles in &line_layouts {
4016 if line_with_invisibles.line.width > max_visible_line_width {
4017 max_visible_line_width = line_with_invisibles.line.width;
4018 }
4019 }
4020
4021 let longest_line_width =
4022 layout_line(snapshot.longest_row(), &snapshot, &style, cx)
4023 .unwrap()
4024 .width;
4025 let mut scroll_width =
4026 longest_line_width.max(max_visible_line_width) + overscroll.width;
4027
4028 let mut blocks = cx.with_element_namespace("blocks", |cx| {
4029 self.build_blocks(
4030 start_row..end_row,
4031 &snapshot,
4032 &hitbox,
4033 &text_hitbox,
4034 &mut scroll_width,
4035 &gutter_dimensions,
4036 em_width,
4037 gutter_dimensions.width + gutter_dimensions.margin,
4038 line_height,
4039 &line_layouts,
4040 cx,
4041 )
4042 });
4043
4044 let scroll_pixel_position = point(
4045 scroll_position.x * em_width,
4046 scroll_position.y * line_height,
4047 );
4048
4049 let mut inline_blame = None;
4050 if let Some(newest_selection_head) = newest_selection_head {
4051 let display_row = newest_selection_head.row();
4052 if (start_row..end_row).contains(&display_row) {
4053 let line_layout = &line_layouts[display_row.minus(start_row) as usize];
4054 inline_blame = self.layout_inline_blame(
4055 display_row,
4056 &snapshot.display_snapshot,
4057 line_layout,
4058 em_width,
4059 content_origin,
4060 scroll_pixel_position,
4061 line_height,
4062 cx,
4063 );
4064 }
4065 }
4066
4067 let blamed_display_rows = self.layout_blame_entries(
4068 buffer_rows.into_iter(),
4069 em_width,
4070 scroll_position,
4071 line_height,
4072 &gutter_hitbox,
4073 gutter_dimensions.git_blame_entries_width,
4074 cx,
4075 );
4076
4077 let scroll_max = point(
4078 ((scroll_width - text_hitbox.size.width) / em_width).max(0.0),
4079 max_row.as_f32(),
4080 );
4081
4082 self.editor.update(cx, |editor, cx| {
4083 let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
4084
4085 let autoscrolled = if autoscroll_horizontally {
4086 editor.autoscroll_horizontally(
4087 start_row,
4088 text_hitbox.size.width,
4089 scroll_width,
4090 em_width,
4091 &line_layouts,
4092 cx,
4093 )
4094 } else {
4095 false
4096 };
4097
4098 if clamped || autoscrolled {
4099 snapshot = editor.snapshot(cx);
4100 scroll_position = snapshot.scroll_position();
4101 }
4102 });
4103
4104 cx.with_element_namespace("blocks", |cx| {
4105 self.layout_blocks(
4106 &mut blocks,
4107 &hitbox,
4108 line_height,
4109 scroll_pixel_position,
4110 cx,
4111 );
4112 });
4113
4114 let cursors = self.collect_cursors(&snapshot, cx);
4115 let visible_row_range = start_row..end_row;
4116 let non_visible_cursors = cursors
4117 .iter()
4118 .any(move |c| !visible_row_range.contains(&c.0.row()));
4119
4120 let visible_cursors = self.layout_visible_cursors(
4121 &snapshot,
4122 &selections,
4123 start_row..end_row,
4124 &line_layouts,
4125 &text_hitbox,
4126 content_origin,
4127 scroll_position,
4128 scroll_pixel_position,
4129 line_height,
4130 em_width,
4131 autoscroll_containing_element,
4132 cx,
4133 );
4134
4135 let scrollbar_layout = self.layout_scrollbar(
4136 &snapshot,
4137 bounds,
4138 scroll_position,
4139 height_in_lines,
4140 non_visible_cursors,
4141 cx,
4142 );
4143
4144 let folds = cx.with_element_namespace("folds", |cx| {
4145 self.layout_folds(
4146 &snapshot,
4147 content_origin,
4148 start_anchor..end_anchor,
4149 start_row..end_row,
4150 scroll_pixel_position,
4151 line_height,
4152 &line_layouts,
4153 cx,
4154 )
4155 });
4156
4157 let gutter_settings = EditorSettings::get_global(cx).gutter;
4158
4159 let mut context_menu_visible = false;
4160 let mut code_actions_indicator = None;
4161 if let Some(newest_selection_head) = newest_selection_head {
4162 if (start_row..end_row).contains(&newest_selection_head.row()) {
4163 context_menu_visible = self.layout_context_menu(
4164 line_height,
4165 &hitbox,
4166 &text_hitbox,
4167 content_origin,
4168 start_row,
4169 scroll_pixel_position,
4170 &line_layouts,
4171 newest_selection_head,
4172 gutter_dimensions.width - gutter_dimensions.left_padding,
4173 cx,
4174 );
4175 if gutter_settings.code_actions {
4176 let newest_selection_point =
4177 newest_selection_head.to_point(&snapshot.display_snapshot);
4178 let buffer = snapshot.buffer_snapshot.buffer_line_for_row(
4179 MultiBufferRow(newest_selection_point.row),
4180 );
4181 if let Some((buffer, range)) = buffer {
4182 let buffer_id = buffer.remote_id();
4183 let row = range.start.row;
4184 let has_test_indicator =
4185 self.editor.read(cx).tasks.contains_key(&(buffer_id, row));
4186
4187 if !has_test_indicator {
4188 code_actions_indicator = self
4189 .layout_code_actions_indicator(
4190 line_height,
4191 newest_selection_head,
4192 scroll_pixel_position,
4193 &gutter_dimensions,
4194 &gutter_hitbox,
4195 cx,
4196 );
4197 }
4198 }
4199 }
4200 }
4201 }
4202
4203 let test_indicators = self.layout_run_indicators(
4204 line_height,
4205 scroll_pixel_position,
4206 &gutter_dimensions,
4207 &gutter_hitbox,
4208 &snapshot,
4209 cx,
4210 );
4211
4212 if !context_menu_visible && !cx.has_active_drag() {
4213 self.layout_hover_popovers(
4214 &snapshot,
4215 &hitbox,
4216 &text_hitbox,
4217 start_row..end_row,
4218 content_origin,
4219 scroll_pixel_position,
4220 &line_layouts,
4221 line_height,
4222 em_width,
4223 cx,
4224 );
4225 }
4226
4227 let mouse_context_menu = self.layout_mouse_context_menu(cx);
4228
4229 let fold_indicators = if gutter_settings.folds {
4230 cx.with_element_namespace("gutter_fold_indicators", |cx| {
4231 self.layout_gutter_fold_indicators(
4232 fold_statuses,
4233 line_height,
4234 &gutter_dimensions,
4235 gutter_settings,
4236 scroll_pixel_position,
4237 &gutter_hitbox,
4238 cx,
4239 )
4240 })
4241 } else {
4242 Vec::new()
4243 };
4244
4245 let invisible_symbol_font_size = font_size / 2.;
4246 let tab_invisible = cx
4247 .text_system()
4248 .shape_line(
4249 "→".into(),
4250 invisible_symbol_font_size,
4251 &[TextRun {
4252 len: "→".len(),
4253 font: self.style.text.font(),
4254 color: cx.theme().colors().editor_invisible,
4255 background_color: None,
4256 underline: None,
4257 strikethrough: None,
4258 }],
4259 )
4260 .unwrap();
4261 let space_invisible = cx
4262 .text_system()
4263 .shape_line(
4264 "•".into(),
4265 invisible_symbol_font_size,
4266 &[TextRun {
4267 len: "•".len(),
4268 font: self.style.text.font(),
4269 color: cx.theme().colors().editor_invisible,
4270 background_color: None,
4271 underline: None,
4272 strikethrough: None,
4273 }],
4274 )
4275 .unwrap();
4276
4277 EditorLayout {
4278 mode: snapshot.mode,
4279 position_map: Arc::new(PositionMap {
4280 size: bounds.size,
4281 scroll_pixel_position,
4282 scroll_max,
4283 line_layouts,
4284 line_height,
4285 em_width,
4286 em_advance,
4287 snapshot,
4288 }),
4289 visible_display_row_range: start_row..end_row,
4290 wrap_guides,
4291 hitbox,
4292 text_hitbox,
4293 gutter_hitbox,
4294 gutter_dimensions,
4295 content_origin,
4296 scrollbar_layout,
4297 active_rows,
4298 highlighted_rows,
4299 highlighted_ranges,
4300 redacted_ranges,
4301 line_numbers,
4302 display_hunks,
4303 blamed_display_rows,
4304 inline_blame,
4305 folds,
4306 blocks,
4307 cursors,
4308 visible_cursors,
4309 selections,
4310 mouse_context_menu,
4311 test_indicators,
4312 code_actions_indicator,
4313 fold_indicators,
4314 tab_invisible,
4315 space_invisible,
4316 }
4317 })
4318 })
4319 })
4320 }
4321
4322 fn paint(
4323 &mut self,
4324 _: Option<&GlobalElementId>,
4325 bounds: Bounds<gpui::Pixels>,
4326 _: &mut Self::RequestLayoutState,
4327 layout: &mut Self::PrepaintState,
4328 cx: &mut WindowContext,
4329 ) {
4330 let focus_handle = self.editor.focus_handle(cx);
4331 let key_context = self.editor.read(cx).key_context(cx);
4332 cx.set_focus_handle(&focus_handle);
4333 cx.set_key_context(key_context);
4334 cx.handle_input(
4335 &focus_handle,
4336 ElementInputHandler::new(bounds, self.editor.clone()),
4337 );
4338 self.register_actions(cx);
4339 self.register_key_listeners(cx, layout);
4340
4341 let text_style = TextStyleRefinement {
4342 font_size: Some(self.style.text.font_size),
4343 line_height: Some(self.style.text.line_height),
4344 ..Default::default()
4345 };
4346 let mouse_position = cx.mouse_position();
4347 let hovered_hunk = layout
4348 .display_hunks
4349 .iter()
4350 .find_map(|(hunk, hunk_hitbox)| match hunk {
4351 DisplayDiffHunk::Folded { .. } => None,
4352 DisplayDiffHunk::Unfolded {
4353 diff_base_byte_range,
4354 multi_buffer_range,
4355 status,
4356 ..
4357 } => {
4358 if hunk_hitbox
4359 .as_ref()
4360 .map(|hitbox| hitbox.contains(&mouse_position))
4361 .unwrap_or(false)
4362 {
4363 Some(HunkToExpand {
4364 status: *status,
4365 multi_buffer_range: multi_buffer_range.clone(),
4366 diff_base_byte_range: diff_base_byte_range.clone(),
4367 })
4368 } else {
4369 None
4370 }
4371 }
4372 });
4373 let rem_size = self.rem_size(cx);
4374 cx.with_rem_size(rem_size, |cx| {
4375 cx.with_text_style(Some(text_style), |cx| {
4376 cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
4377 self.paint_mouse_listeners(layout, hovered_hunk, cx);
4378 self.paint_background(layout, cx);
4379 if layout.gutter_hitbox.size.width > Pixels::ZERO {
4380 self.paint_gutter(layout, cx)
4381 }
4382
4383 self.paint_text(layout, cx);
4384
4385 if !layout.blocks.is_empty() {
4386 cx.with_element_namespace("blocks", |cx| {
4387 self.paint_blocks(layout, cx);
4388 });
4389 }
4390
4391 self.paint_scrollbar(layout, cx);
4392 self.paint_mouse_context_menu(layout, cx);
4393 });
4394 })
4395 })
4396 }
4397}
4398
4399impl IntoElement for EditorElement {
4400 type Element = Self;
4401
4402 fn into_element(self) -> Self::Element {
4403 self
4404 }
4405}
4406
4407pub struct EditorLayout {
4408 position_map: Arc<PositionMap>,
4409 hitbox: Hitbox,
4410 text_hitbox: Hitbox,
4411 gutter_hitbox: Hitbox,
4412 gutter_dimensions: GutterDimensions,
4413 content_origin: gpui::Point<Pixels>,
4414 scrollbar_layout: Option<ScrollbarLayout>,
4415 mode: EditorMode,
4416 wrap_guides: SmallVec<[(Pixels, bool); 2]>,
4417 visible_display_row_range: Range<DisplayRow>,
4418 active_rows: BTreeMap<DisplayRow, bool>,
4419 highlighted_rows: BTreeMap<DisplayRow, Hsla>,
4420 line_numbers: Vec<Option<ShapedLine>>,
4421 display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
4422 blamed_display_rows: Option<Vec<AnyElement>>,
4423 inline_blame: Option<AnyElement>,
4424 folds: Vec<FoldLayout>,
4425 blocks: Vec<BlockLayout>,
4426 highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
4427 redacted_ranges: Vec<Range<DisplayPoint>>,
4428 cursors: Vec<(DisplayPoint, Hsla)>,
4429 visible_cursors: Vec<CursorLayout>,
4430 selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
4431 code_actions_indicator: Option<AnyElement>,
4432 test_indicators: Vec<AnyElement>,
4433 fold_indicators: Vec<Option<AnyElement>>,
4434 mouse_context_menu: Option<AnyElement>,
4435 tab_invisible: ShapedLine,
4436 space_invisible: ShapedLine,
4437}
4438
4439impl EditorLayout {
4440 fn line_end_overshoot(&self) -> Pixels {
4441 0.15 * self.position_map.line_height
4442 }
4443}
4444
4445struct ColoredRange<T> {
4446 start: T,
4447 end: T,
4448 color: Hsla,
4449}
4450
4451#[derive(Clone)]
4452struct ScrollbarLayout {
4453 hitbox: Hitbox,
4454 visible_row_range: Range<f32>,
4455 visible: bool,
4456 row_height: Pixels,
4457 thumb_height: Pixels,
4458}
4459
4460impl ScrollbarLayout {
4461 const BORDER_WIDTH: Pixels = px(1.0);
4462 const LINE_MARKER_HEIGHT: Pixels = px(2.0);
4463 const MIN_MARKER_HEIGHT: Pixels = px(5.0);
4464 const MIN_THUMB_HEIGHT: Pixels = px(20.0);
4465
4466 fn thumb_bounds(&self) -> Bounds<Pixels> {
4467 let thumb_top = self.y_for_row(self.visible_row_range.start);
4468 let thumb_bottom = thumb_top + self.thumb_height;
4469 Bounds::from_corners(
4470 point(self.hitbox.left(), thumb_top),
4471 point(self.hitbox.right(), thumb_bottom),
4472 )
4473 }
4474
4475 fn y_for_row(&self, row: f32) -> Pixels {
4476 self.hitbox.top() + row * self.row_height
4477 }
4478
4479 fn marker_quads_for_ranges(
4480 &self,
4481 row_ranges: impl IntoIterator<Item = ColoredRange<DisplayRow>>,
4482 column: Option<usize>,
4483 ) -> Vec<PaintQuad> {
4484 struct MinMax {
4485 min: Pixels,
4486 max: Pixels,
4487 }
4488 let (x_range, height_limit) = if let Some(column) = column {
4489 let column_width = px(((self.hitbox.size.width - Self::BORDER_WIDTH).0 / 3.0).floor());
4490 let start = Self::BORDER_WIDTH + (column as f32 * column_width);
4491 let end = start + column_width;
4492 (
4493 Range { start, end },
4494 MinMax {
4495 min: Self::MIN_MARKER_HEIGHT,
4496 max: px(f32::MAX),
4497 },
4498 )
4499 } else {
4500 (
4501 Range {
4502 start: Self::BORDER_WIDTH,
4503 end: self.hitbox.size.width,
4504 },
4505 MinMax {
4506 min: Self::LINE_MARKER_HEIGHT,
4507 max: Self::LINE_MARKER_HEIGHT,
4508 },
4509 )
4510 };
4511
4512 let row_to_y = |row: DisplayRow| row.as_f32() * self.row_height;
4513 let mut pixel_ranges = row_ranges
4514 .into_iter()
4515 .map(|range| {
4516 let start_y = row_to_y(range.start);
4517 let end_y = row_to_y(range.end)
4518 + self.row_height.max(height_limit.min).min(height_limit.max);
4519 ColoredRange {
4520 start: start_y,
4521 end: end_y,
4522 color: range.color,
4523 }
4524 })
4525 .peekable();
4526
4527 let mut quads = Vec::new();
4528 while let Some(mut pixel_range) = pixel_ranges.next() {
4529 while let Some(next_pixel_range) = pixel_ranges.peek() {
4530 if pixel_range.end >= next_pixel_range.start - px(1.0)
4531 && pixel_range.color == next_pixel_range.color
4532 {
4533 pixel_range.end = next_pixel_range.end.max(pixel_range.end);
4534 pixel_ranges.next();
4535 } else {
4536 break;
4537 }
4538 }
4539
4540 let bounds = Bounds::from_corners(
4541 point(x_range.start, pixel_range.start),
4542 point(x_range.end, pixel_range.end),
4543 );
4544 quads.push(quad(
4545 bounds,
4546 Corners::default(),
4547 pixel_range.color,
4548 Edges::default(),
4549 Hsla::transparent_black(),
4550 ));
4551 }
4552
4553 quads
4554 }
4555}
4556
4557struct FoldLayout {
4558 display_range: Range<DisplayPoint>,
4559 hover_element: AnyElement,
4560}
4561
4562struct PositionMap {
4563 size: Size<Pixels>,
4564 line_height: Pixels,
4565 scroll_pixel_position: gpui::Point<Pixels>,
4566 scroll_max: gpui::Point<f32>,
4567 em_width: Pixels,
4568 em_advance: Pixels,
4569 line_layouts: Vec<LineWithInvisibles>,
4570 snapshot: EditorSnapshot,
4571}
4572
4573#[derive(Debug, Copy, Clone)]
4574pub struct PointForPosition {
4575 pub previous_valid: DisplayPoint,
4576 pub next_valid: DisplayPoint,
4577 pub exact_unclipped: DisplayPoint,
4578 pub column_overshoot_after_line_end: u32,
4579}
4580
4581impl PointForPosition {
4582 pub fn as_valid(&self) -> Option<DisplayPoint> {
4583 if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped {
4584 Some(self.previous_valid)
4585 } else {
4586 None
4587 }
4588 }
4589}
4590
4591impl PositionMap {
4592 fn point_for_position(
4593 &self,
4594 text_bounds: Bounds<Pixels>,
4595 position: gpui::Point<Pixels>,
4596 ) -> PointForPosition {
4597 let scroll_position = self.snapshot.scroll_position();
4598 let position = position - text_bounds.origin;
4599 let y = position.y.max(px(0.)).min(self.size.height);
4600 let x = position.x + (scroll_position.x * self.em_width);
4601 let row = ((y / self.line_height) + scroll_position.y) as u32;
4602
4603 let (column, x_overshoot_after_line_end) = if let Some(line) = self
4604 .line_layouts
4605 .get(row as usize - scroll_position.y as usize)
4606 .map(|LineWithInvisibles { line, .. }| line)
4607 {
4608 if let Some(ix) = line.index_for_x(x) {
4609 (ix as u32, px(0.))
4610 } else {
4611 (line.len as u32, px(0.).max(x - line.width))
4612 }
4613 } else {
4614 (0, x)
4615 };
4616
4617 let mut exact_unclipped = DisplayPoint::new(DisplayRow(row), column);
4618 let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
4619 let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
4620
4621 let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32;
4622 *exact_unclipped.column_mut() += column_overshoot_after_line_end;
4623 PointForPosition {
4624 previous_valid,
4625 next_valid,
4626 exact_unclipped,
4627 column_overshoot_after_line_end,
4628 }
4629 }
4630}
4631
4632struct BlockLayout {
4633 row: DisplayRow,
4634 element: AnyElement,
4635 available_space: Size<AvailableSpace>,
4636 style: BlockStyle,
4637}
4638
4639fn layout_line(
4640 row: DisplayRow,
4641 snapshot: &EditorSnapshot,
4642 style: &EditorStyle,
4643 cx: &WindowContext,
4644) -> Result<ShapedLine> {
4645 let mut line = snapshot.line(row);
4646
4647 let len = {
4648 let line_len = line.len();
4649 if line_len > MAX_LINE_LEN {
4650 let mut len = MAX_LINE_LEN;
4651 while !line.is_char_boundary(len) {
4652 len -= 1;
4653 }
4654
4655 line.truncate(len);
4656 len
4657 } else {
4658 line_len
4659 }
4660 };
4661
4662 cx.text_system().shape_line(
4663 line.into(),
4664 style.text.font_size.to_pixels(cx.rem_size()),
4665 &[TextRun {
4666 len,
4667 font: style.text.font(),
4668 color: Hsla::default(),
4669 background_color: None,
4670 underline: None,
4671 strikethrough: None,
4672 }],
4673 )
4674}
4675
4676pub struct CursorLayout {
4677 origin: gpui::Point<Pixels>,
4678 block_width: Pixels,
4679 line_height: Pixels,
4680 color: Hsla,
4681 shape: CursorShape,
4682 block_text: Option<ShapedLine>,
4683 cursor_name: Option<AnyElement>,
4684}
4685
4686#[derive(Debug)]
4687pub struct CursorName {
4688 string: SharedString,
4689 color: Hsla,
4690 is_top_row: bool,
4691}
4692
4693impl CursorLayout {
4694 pub fn new(
4695 origin: gpui::Point<Pixels>,
4696 block_width: Pixels,
4697 line_height: Pixels,
4698 color: Hsla,
4699 shape: CursorShape,
4700 block_text: Option<ShapedLine>,
4701 ) -> CursorLayout {
4702 CursorLayout {
4703 origin,
4704 block_width,
4705 line_height,
4706 color,
4707 shape,
4708 block_text,
4709 cursor_name: None,
4710 }
4711 }
4712
4713 pub fn bounding_rect(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
4714 Bounds {
4715 origin: self.origin + origin,
4716 size: size(self.block_width, self.line_height),
4717 }
4718 }
4719
4720 fn bounds(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
4721 match self.shape {
4722 CursorShape::Bar => Bounds {
4723 origin: self.origin + origin,
4724 size: size(px(2.0), self.line_height),
4725 },
4726 CursorShape::Block | CursorShape::Hollow => Bounds {
4727 origin: self.origin + origin,
4728 size: size(self.block_width, self.line_height),
4729 },
4730 CursorShape::Underscore => Bounds {
4731 origin: self.origin
4732 + origin
4733 + gpui::Point::new(Pixels::ZERO, self.line_height - px(2.0)),
4734 size: size(self.block_width, px(2.0)),
4735 },
4736 }
4737 }
4738
4739 pub fn layout(
4740 &mut self,
4741 origin: gpui::Point<Pixels>,
4742 cursor_name: Option<CursorName>,
4743 cx: &mut WindowContext,
4744 ) {
4745 if let Some(cursor_name) = cursor_name {
4746 let bounds = self.bounds(origin);
4747 let text_size = self.line_height / 1.5;
4748
4749 let name_origin = if cursor_name.is_top_row {
4750 point(bounds.right() - px(1.), bounds.top())
4751 } else {
4752 point(bounds.left(), bounds.top() - text_size / 2. - px(1.))
4753 };
4754 let mut name_element = div()
4755 .bg(self.color)
4756 .text_size(text_size)
4757 .px_0p5()
4758 .line_height(text_size + px(2.))
4759 .text_color(cursor_name.color)
4760 .child(cursor_name.string.clone())
4761 .into_any_element();
4762
4763 name_element.prepaint_as_root(
4764 name_origin,
4765 size(AvailableSpace::MinContent, AvailableSpace::MinContent),
4766 cx,
4767 );
4768
4769 self.cursor_name = Some(name_element);
4770 }
4771 }
4772
4773 pub fn paint(&mut self, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
4774 let bounds = self.bounds(origin);
4775
4776 //Draw background or border quad
4777 let cursor = if matches!(self.shape, CursorShape::Hollow) {
4778 outline(bounds, self.color)
4779 } else {
4780 fill(bounds, self.color)
4781 };
4782
4783 if let Some(name) = &mut self.cursor_name {
4784 name.paint(cx);
4785 }
4786
4787 cx.paint_quad(cursor);
4788
4789 if let Some(block_text) = &self.block_text {
4790 block_text
4791 .paint(self.origin + origin, self.line_height, cx)
4792 .log_err();
4793 }
4794 }
4795
4796 pub fn shape(&self) -> CursorShape {
4797 self.shape
4798 }
4799}
4800
4801#[derive(Debug)]
4802pub struct HighlightedRange {
4803 pub start_y: Pixels,
4804 pub line_height: Pixels,
4805 pub lines: Vec<HighlightedRangeLine>,
4806 pub color: Hsla,
4807 pub corner_radius: Pixels,
4808}
4809
4810#[derive(Debug)]
4811pub struct HighlightedRangeLine {
4812 pub start_x: Pixels,
4813 pub end_x: Pixels,
4814}
4815
4816impl HighlightedRange {
4817 pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
4818 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
4819 self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
4820 self.paint_lines(
4821 self.start_y + self.line_height,
4822 &self.lines[1..],
4823 bounds,
4824 cx,
4825 );
4826 } else {
4827 self.paint_lines(self.start_y, &self.lines, bounds, cx);
4828 }
4829 }
4830
4831 fn paint_lines(
4832 &self,
4833 start_y: Pixels,
4834 lines: &[HighlightedRangeLine],
4835 _bounds: Bounds<Pixels>,
4836 cx: &mut WindowContext,
4837 ) {
4838 if lines.is_empty() {
4839 return;
4840 }
4841
4842 let first_line = lines.first().unwrap();
4843 let last_line = lines.last().unwrap();
4844
4845 let first_top_left = point(first_line.start_x, start_y);
4846 let first_top_right = point(first_line.end_x, start_y);
4847
4848 let curve_height = point(Pixels::ZERO, self.corner_radius);
4849 let curve_width = |start_x: Pixels, end_x: Pixels| {
4850 let max = (end_x - start_x) / 2.;
4851 let width = if max < self.corner_radius {
4852 max
4853 } else {
4854 self.corner_radius
4855 };
4856
4857 point(width, Pixels::ZERO)
4858 };
4859
4860 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
4861 let mut path = gpui::Path::new(first_top_right - top_curve_width);
4862 path.curve_to(first_top_right + curve_height, first_top_right);
4863
4864 let mut iter = lines.iter().enumerate().peekable();
4865 while let Some((ix, line)) = iter.next() {
4866 let bottom_right = point(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
4867
4868 if let Some((_, next_line)) = iter.peek() {
4869 let next_top_right = point(next_line.end_x, bottom_right.y);
4870
4871 match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
4872 Ordering::Equal => {
4873 path.line_to(bottom_right);
4874 }
4875 Ordering::Less => {
4876 let curve_width = curve_width(next_top_right.x, bottom_right.x);
4877 path.line_to(bottom_right - curve_height);
4878 if self.corner_radius > Pixels::ZERO {
4879 path.curve_to(bottom_right - curve_width, bottom_right);
4880 }
4881 path.line_to(next_top_right + curve_width);
4882 if self.corner_radius > Pixels::ZERO {
4883 path.curve_to(next_top_right + curve_height, next_top_right);
4884 }
4885 }
4886 Ordering::Greater => {
4887 let curve_width = curve_width(bottom_right.x, next_top_right.x);
4888 path.line_to(bottom_right - curve_height);
4889 if self.corner_radius > Pixels::ZERO {
4890 path.curve_to(bottom_right + curve_width, bottom_right);
4891 }
4892 path.line_to(next_top_right - curve_width);
4893 if self.corner_radius > Pixels::ZERO {
4894 path.curve_to(next_top_right + curve_height, next_top_right);
4895 }
4896 }
4897 }
4898 } else {
4899 let curve_width = curve_width(line.start_x, line.end_x);
4900 path.line_to(bottom_right - curve_height);
4901 if self.corner_radius > Pixels::ZERO {
4902 path.curve_to(bottom_right - curve_width, bottom_right);
4903 }
4904
4905 let bottom_left = point(line.start_x, bottom_right.y);
4906 path.line_to(bottom_left + curve_width);
4907 if self.corner_radius > Pixels::ZERO {
4908 path.curve_to(bottom_left - curve_height, bottom_left);
4909 }
4910 }
4911 }
4912
4913 if first_line.start_x > last_line.start_x {
4914 let curve_width = curve_width(last_line.start_x, first_line.start_x);
4915 let second_top_left = point(last_line.start_x, start_y + self.line_height);
4916 path.line_to(second_top_left + curve_height);
4917 if self.corner_radius > Pixels::ZERO {
4918 path.curve_to(second_top_left + curve_width, second_top_left);
4919 }
4920 let first_bottom_left = point(first_line.start_x, second_top_left.y);
4921 path.line_to(first_bottom_left - curve_width);
4922 if self.corner_radius > Pixels::ZERO {
4923 path.curve_to(first_bottom_left - curve_height, first_bottom_left);
4924 }
4925 }
4926
4927 path.line_to(first_top_left + curve_height);
4928 if self.corner_radius > Pixels::ZERO {
4929 path.curve_to(first_top_left + top_curve_width, first_top_left);
4930 }
4931 path.line_to(first_top_right - top_curve_width);
4932
4933 cx.paint_path(path, self.color);
4934 }
4935}
4936
4937pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
4938 (delta.pow(1.5) / 100.0).into()
4939}
4940
4941fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
4942 (delta.pow(1.2) / 300.0).into()
4943}
4944
4945#[cfg(test)]
4946mod tests {
4947 use super::*;
4948 use crate::{
4949 display_map::{BlockDisposition, BlockProperties},
4950 editor_tests::{init_test, update_test_language_settings},
4951 Editor, MultiBuffer,
4952 };
4953 use gpui::{TestAppContext, VisualTestContext};
4954 use language::language_settings;
4955 use log::info;
4956 use std::num::NonZeroU32;
4957 use ui::Context;
4958 use util::test::sample_text;
4959
4960 #[gpui::test]
4961 fn test_shape_line_numbers(cx: &mut TestAppContext) {
4962 init_test(cx, |_| {});
4963 let window = cx.add_window(|cx| {
4964 let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
4965 Editor::new(EditorMode::Full, buffer, None, cx)
4966 });
4967
4968 let editor = window.root(cx).unwrap();
4969 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
4970 let element = EditorElement::new(&editor, style);
4971 let snapshot = window.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
4972
4973 let layouts = cx
4974 .update_window(*window, |_, cx| {
4975 element
4976 .layout_line_numbers(
4977 DisplayRow(0)..DisplayRow(6),
4978 (0..6).map(MultiBufferRow).map(Some),
4979 &Default::default(),
4980 Some(DisplayPoint::new(DisplayRow(0), 0)),
4981 &snapshot,
4982 cx,
4983 )
4984 .0
4985 })
4986 .unwrap();
4987 assert_eq!(layouts.len(), 6);
4988
4989 let relative_rows = window
4990 .update(cx, |editor, cx| {
4991 let snapshot = editor.snapshot(cx);
4992 element.calculate_relative_line_numbers(
4993 &snapshot,
4994 &(DisplayRow(0)..DisplayRow(6)),
4995 Some(DisplayRow(3)),
4996 )
4997 })
4998 .unwrap();
4999 assert_eq!(relative_rows[&DisplayRow(0)], 3);
5000 assert_eq!(relative_rows[&DisplayRow(1)], 2);
5001 assert_eq!(relative_rows[&DisplayRow(2)], 1);
5002 // current line has no relative number
5003 assert_eq!(relative_rows[&DisplayRow(4)], 1);
5004 assert_eq!(relative_rows[&DisplayRow(5)], 2);
5005
5006 // works if cursor is before screen
5007 let relative_rows = window
5008 .update(cx, |editor, cx| {
5009 let snapshot = editor.snapshot(cx);
5010 element.calculate_relative_line_numbers(
5011 &snapshot,
5012 &(DisplayRow(3)..DisplayRow(6)),
5013 Some(DisplayRow(1)),
5014 )
5015 })
5016 .unwrap();
5017 assert_eq!(relative_rows.len(), 3);
5018 assert_eq!(relative_rows[&DisplayRow(3)], 2);
5019 assert_eq!(relative_rows[&DisplayRow(4)], 3);
5020 assert_eq!(relative_rows[&DisplayRow(5)], 4);
5021
5022 // works if cursor is after screen
5023 let relative_rows = window
5024 .update(cx, |editor, cx| {
5025 let snapshot = editor.snapshot(cx);
5026 element.calculate_relative_line_numbers(
5027 &snapshot,
5028 &(DisplayRow(0)..DisplayRow(3)),
5029 Some(DisplayRow(6)),
5030 )
5031 })
5032 .unwrap();
5033 assert_eq!(relative_rows.len(), 3);
5034 assert_eq!(relative_rows[&DisplayRow(0)], 5);
5035 assert_eq!(relative_rows[&DisplayRow(1)], 4);
5036 assert_eq!(relative_rows[&DisplayRow(2)], 3);
5037 }
5038
5039 #[gpui::test]
5040 async fn test_vim_visual_selections(cx: &mut TestAppContext) {
5041 init_test(cx, |_| {});
5042
5043 let window = cx.add_window(|cx| {
5044 let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
5045 Editor::new(EditorMode::Full, buffer, None, cx)
5046 });
5047 let cx = &mut VisualTestContext::from_window(*window, cx);
5048 let editor = window.root(cx).unwrap();
5049 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
5050
5051 window
5052 .update(cx, |editor, cx| {
5053 editor.cursor_shape = CursorShape::Block;
5054 editor.change_selections(None, cx, |s| {
5055 s.select_ranges([
5056 Point::new(0, 0)..Point::new(1, 0),
5057 Point::new(3, 2)..Point::new(3, 3),
5058 Point::new(5, 6)..Point::new(6, 0),
5059 ]);
5060 });
5061 })
5062 .unwrap();
5063
5064 let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
5065 EditorElement::new(&editor, style)
5066 });
5067
5068 assert_eq!(state.selections.len(), 1);
5069 let local_selections = &state.selections[0].1;
5070 assert_eq!(local_selections.len(), 3);
5071 // moves cursor back one line
5072 assert_eq!(
5073 local_selections[0].head,
5074 DisplayPoint::new(DisplayRow(0), 6)
5075 );
5076 assert_eq!(
5077 local_selections[0].range,
5078 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0)
5079 );
5080
5081 // moves cursor back one column
5082 assert_eq!(
5083 local_selections[1].range,
5084 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 3)
5085 );
5086 assert_eq!(
5087 local_selections[1].head,
5088 DisplayPoint::new(DisplayRow(3), 2)
5089 );
5090
5091 // leaves cursor on the max point
5092 assert_eq!(
5093 local_selections[2].range,
5094 DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(6), 0)
5095 );
5096 assert_eq!(
5097 local_selections[2].head,
5098 DisplayPoint::new(DisplayRow(6), 0)
5099 );
5100
5101 // active lines does not include 1 (even though the range of the selection does)
5102 assert_eq!(
5103 state.active_rows.keys().cloned().collect::<Vec<_>>(),
5104 vec![DisplayRow(0), DisplayRow(3), DisplayRow(5), DisplayRow(6)]
5105 );
5106
5107 // multi-buffer support
5108 // in DisplayPoint coordinates, this is what we're dealing with:
5109 // 0: [[file
5110 // 1: header]]
5111 // 2: aaaaaa
5112 // 3: bbbbbb
5113 // 4: cccccc
5114 // 5:
5115 // 6: ...
5116 // 7: ffffff
5117 // 8: gggggg
5118 // 9: hhhhhh
5119 // 10:
5120 // 11: [[file
5121 // 12: header]]
5122 // 13: bbbbbb
5123 // 14: cccccc
5124 // 15: dddddd
5125 let window = cx.add_window(|cx| {
5126 let buffer = MultiBuffer::build_multi(
5127 [
5128 (
5129 &(sample_text(8, 6, 'a') + "\n"),
5130 vec![
5131 Point::new(0, 0)..Point::new(3, 0),
5132 Point::new(4, 0)..Point::new(7, 0),
5133 ],
5134 ),
5135 (
5136 &(sample_text(8, 6, 'a') + "\n"),
5137 vec![Point::new(1, 0)..Point::new(3, 0)],
5138 ),
5139 ],
5140 cx,
5141 );
5142 Editor::new(EditorMode::Full, buffer, None, cx)
5143 });
5144 let editor = window.root(cx).unwrap();
5145 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
5146 let _state = window.update(cx, |editor, cx| {
5147 editor.cursor_shape = CursorShape::Block;
5148 editor.change_selections(None, cx, |s| {
5149 s.select_display_ranges([
5150 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(7), 0),
5151 DisplayPoint::new(DisplayRow(10), 0)..DisplayPoint::new(DisplayRow(13), 0),
5152 ]);
5153 });
5154 });
5155
5156 let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
5157 EditorElement::new(&editor, style)
5158 });
5159 assert_eq!(state.selections.len(), 1);
5160 let local_selections = &state.selections[0].1;
5161 assert_eq!(local_selections.len(), 2);
5162
5163 // moves cursor on excerpt boundary back a line
5164 // and doesn't allow selection to bleed through
5165 assert_eq!(
5166 local_selections[0].range,
5167 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(6), 0)
5168 );
5169 assert_eq!(
5170 local_selections[0].head,
5171 DisplayPoint::new(DisplayRow(5), 0)
5172 );
5173 // moves cursor on buffer boundary back two lines
5174 // and doesn't allow selection to bleed through
5175 assert_eq!(
5176 local_selections[1].range,
5177 DisplayPoint::new(DisplayRow(10), 0)..DisplayPoint::new(DisplayRow(11), 0)
5178 );
5179 assert_eq!(
5180 local_selections[1].head,
5181 DisplayPoint::new(DisplayRow(10), 0)
5182 );
5183 }
5184
5185 #[gpui::test]
5186 fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
5187 init_test(cx, |_| {});
5188
5189 let window = cx.add_window(|cx| {
5190 let buffer = MultiBuffer::build_simple("", cx);
5191 Editor::new(EditorMode::Full, buffer, None, cx)
5192 });
5193 let cx = &mut VisualTestContext::from_window(*window, cx);
5194 let editor = window.root(cx).unwrap();
5195 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
5196 window
5197 .update(cx, |editor, cx| {
5198 editor.set_placeholder_text("hello", cx);
5199 editor.insert_blocks(
5200 [BlockProperties {
5201 style: BlockStyle::Fixed,
5202 disposition: BlockDisposition::Above,
5203 height: 3,
5204 position: Anchor::min(),
5205 render: Box::new(|_| div().into_any()),
5206 }],
5207 None,
5208 cx,
5209 );
5210
5211 // Blur the editor so that it displays placeholder text.
5212 cx.blur();
5213 })
5214 .unwrap();
5215
5216 let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
5217 EditorElement::new(&editor, style)
5218 });
5219 assert_eq!(state.position_map.line_layouts.len(), 4);
5220 assert_eq!(
5221 state
5222 .line_numbers
5223 .iter()
5224 .map(Option::is_some)
5225 .collect::<Vec<_>>(),
5226 &[false, false, false, true]
5227 );
5228 }
5229
5230 #[gpui::test]
5231 fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
5232 const TAB_SIZE: u32 = 4;
5233
5234 let input_text = "\t \t|\t| a b";
5235 let expected_invisibles = vec![
5236 Invisible::Tab {
5237 line_start_offset: 0,
5238 },
5239 Invisible::Whitespace {
5240 line_offset: TAB_SIZE as usize,
5241 },
5242 Invisible::Tab {
5243 line_start_offset: TAB_SIZE as usize + 1,
5244 },
5245 Invisible::Tab {
5246 line_start_offset: TAB_SIZE as usize * 2 + 1,
5247 },
5248 Invisible::Whitespace {
5249 line_offset: TAB_SIZE as usize * 3 + 1,
5250 },
5251 Invisible::Whitespace {
5252 line_offset: TAB_SIZE as usize * 3 + 3,
5253 },
5254 ];
5255 assert_eq!(
5256 expected_invisibles.len(),
5257 input_text
5258 .chars()
5259 .filter(|initial_char| initial_char.is_whitespace())
5260 .count(),
5261 "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
5262 );
5263
5264 init_test(cx, |s| {
5265 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
5266 s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
5267 });
5268
5269 let actual_invisibles =
5270 collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, px(500.0));
5271
5272 assert_eq!(expected_invisibles, actual_invisibles);
5273 }
5274
5275 #[gpui::test]
5276 fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
5277 init_test(cx, |s| {
5278 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
5279 s.defaults.tab_size = NonZeroU32::new(4);
5280 });
5281
5282 for editor_mode_without_invisibles in [
5283 EditorMode::SingleLine,
5284 EditorMode::AutoHeight { max_lines: 100 },
5285 ] {
5286 let invisibles = collect_invisibles_from_new_editor(
5287 cx,
5288 editor_mode_without_invisibles,
5289 "\t\t\t| | a b",
5290 px(500.0),
5291 );
5292 assert!(invisibles.is_empty(),
5293 "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}");
5294 }
5295 }
5296
5297 #[gpui::test]
5298 fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
5299 let tab_size = 4;
5300 let input_text = "a\tbcd ".repeat(9);
5301 let repeated_invisibles = [
5302 Invisible::Tab {
5303 line_start_offset: 1,
5304 },
5305 Invisible::Whitespace {
5306 line_offset: tab_size as usize + 3,
5307 },
5308 Invisible::Whitespace {
5309 line_offset: tab_size as usize + 4,
5310 },
5311 Invisible::Whitespace {
5312 line_offset: tab_size as usize + 5,
5313 },
5314 ];
5315 let expected_invisibles = std::iter::once(repeated_invisibles)
5316 .cycle()
5317 .take(9)
5318 .flatten()
5319 .collect::<Vec<_>>();
5320 assert_eq!(
5321 expected_invisibles.len(),
5322 input_text
5323 .chars()
5324 .filter(|initial_char| initial_char.is_whitespace())
5325 .count(),
5326 "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
5327 );
5328 info!("Expected invisibles: {expected_invisibles:?}");
5329
5330 init_test(cx, |_| {});
5331
5332 // Put the same string with repeating whitespace pattern into editors of various size,
5333 // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
5334 let resize_step = 10.0;
5335 let mut editor_width = 200.0;
5336 while editor_width <= 1000.0 {
5337 update_test_language_settings(cx, |s| {
5338 s.defaults.tab_size = NonZeroU32::new(tab_size);
5339 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
5340 s.defaults.preferred_line_length = Some(editor_width as u32);
5341 s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
5342 });
5343
5344 let actual_invisibles = collect_invisibles_from_new_editor(
5345 cx,
5346 EditorMode::Full,
5347 &input_text,
5348 px(editor_width),
5349 );
5350
5351 // Whatever the editor size is, ensure it has the same invisible kinds in the same order
5352 // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
5353 let mut i = 0;
5354 for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
5355 i = actual_index;
5356 match expected_invisibles.get(i) {
5357 Some(expected_invisible) => match (expected_invisible, actual_invisible) {
5358 (Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
5359 | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
5360 _ => {
5361 panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}")
5362 }
5363 },
5364 None => panic!("Unexpected extra invisible {actual_invisible:?} at index {i}"),
5365 }
5366 }
5367 let missing_expected_invisibles = &expected_invisibles[i + 1..];
5368 assert!(
5369 missing_expected_invisibles.is_empty(),
5370 "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
5371 );
5372
5373 editor_width += resize_step;
5374 }
5375 }
5376
5377 fn collect_invisibles_from_new_editor(
5378 cx: &mut TestAppContext,
5379 editor_mode: EditorMode,
5380 input_text: &str,
5381 editor_width: Pixels,
5382 ) -> Vec<Invisible> {
5383 info!(
5384 "Creating editor with mode {editor_mode:?}, width {}px and text '{input_text}'",
5385 editor_width.0
5386 );
5387 let window = cx.add_window(|cx| {
5388 let buffer = MultiBuffer::build_simple(&input_text, cx);
5389 Editor::new(editor_mode, buffer, None, cx)
5390 });
5391 let cx = &mut VisualTestContext::from_window(*window, cx);
5392 let editor = window.root(cx).unwrap();
5393 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
5394 window
5395 .update(cx, |editor, cx| {
5396 editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
5397 editor.set_wrap_width(Some(editor_width), cx);
5398 })
5399 .unwrap();
5400 let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
5401 EditorElement::new(&editor, style)
5402 });
5403 state
5404 .position_map
5405 .line_layouts
5406 .iter()
5407 .flat_map(|line_with_invisibles| &line_with_invisibles.invisibles)
5408 .cloned()
5409 .collect()
5410 }
5411}
5412
5413pub fn register_action<T: Action>(
5414 view: &View<Editor>,
5415 cx: &mut WindowContext,
5416 listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
5417) {
5418 let view = view.clone();
5419 cx.on_action(TypeId::of::<T>(), move |action, phase, cx| {
5420 let action = action.downcast_ref().unwrap();
5421 if phase == DispatchPhase::Bubble {
5422 view.update(cx, |editor, cx| {
5423 listener(editor, action, cx);
5424 })
5425 }
5426 })
5427}
5428
5429fn compute_auto_height_layout(
5430 editor: &mut Editor,
5431 max_lines: usize,
5432 max_line_number_width: Pixels,
5433 known_dimensions: Size<Option<Pixels>>,
5434 available_width: AvailableSpace,
5435 cx: &mut ViewContext<Editor>,
5436) -> Option<Size<Pixels>> {
5437 let width = known_dimensions.width.or_else(|| {
5438 if let AvailableSpace::Definite(available_width) = available_width {
5439 Some(available_width)
5440 } else {
5441 None
5442 }
5443 })?;
5444 if let Some(height) = known_dimensions.height {
5445 return Some(size(width, height));
5446 }
5447
5448 let style = editor.style.as_ref().unwrap();
5449 let font_id = cx.text_system().resolve_font(&style.text.font());
5450 let font_size = style.text.font_size.to_pixels(cx.rem_size());
5451 let line_height = style.text.line_height_in_pixels(cx.rem_size());
5452 let em_width = cx
5453 .text_system()
5454 .typographic_bounds(font_id, font_size, 'm')
5455 .unwrap()
5456 .size
5457 .width;
5458
5459 let mut snapshot = editor.snapshot(cx);
5460 let gutter_dimensions =
5461 snapshot.gutter_dimensions(font_id, font_size, em_width, max_line_number_width, cx);
5462
5463 editor.gutter_dimensions = gutter_dimensions;
5464 let text_width = width - gutter_dimensions.width;
5465 let overscroll = size(em_width, px(0.));
5466
5467 let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width;
5468 if editor.set_wrap_width(Some(editor_width), cx) {
5469 snapshot = editor.snapshot(cx);
5470 }
5471
5472 let scroll_height = Pixels::from(snapshot.max_point().row().next_row().0) * line_height;
5473 let height = scroll_height
5474 .max(line_height)
5475 .min(line_height * max_lines as f32);
5476
5477 Some(size(width, height))
5478}