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