1use crate::{
2 display_map::{
3 BlockContext, BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, ToDisplayPoint,
4 TransformBlock,
5 },
6 editor_settings::ShowScrollbar,
7 git::{diff_hunk_to_display, DisplayDiffHunk},
8 hover_popover::{
9 self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
10 },
11 items::BufferSearchHighlights,
12 mouse_context_menu,
13 scroll::scroll_amount::ScrollAmount,
14 CursorShape, DisplayPoint, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode,
15 EditorSettings, EditorSnapshot, EditorStyle, HalfPageDown, HalfPageUp, HoveredCursor, LineDown,
16 LineUp, OpenExcerpts, PageDown, PageUp, Point, SelectPhase, Selection, SoftWrap, ToPoint,
17 CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
18};
19use anyhow::Result;
20use collections::{BTreeMap, HashMap};
21use git::diff::DiffHunkStatus;
22use gpui::{
23 div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action,
24 AnchorCorner, AnyElement, AvailableSpace, Bounds, ContentMask, Corners, CursorStyle,
25 DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, InteractiveBounds,
26 InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent,
27 MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine,
28 SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun,
29 TextStyle, View, ViewContext, WindowContext,
30};
31use itertools::Itertools;
32use language::language_settings::ShowWhitespaceSetting;
33use lsp::DiagnosticSeverity;
34use multi_buffer::Anchor;
35use project::{
36 project_settings::{GitGutterSetting, ProjectSettings},
37 ProjectPath,
38};
39use settings::Settings;
40use smallvec::SmallVec;
41use std::{
42 any::TypeId,
43 borrow::Cow,
44 cmp::{self, Ordering},
45 fmt::Write,
46 iter,
47 ops::Range,
48 sync::Arc,
49};
50use sum_tree::Bias;
51use theme::{ActiveTheme, PlayerColor};
52use ui::prelude::*;
53use ui::{h_flex, ButtonLike, ButtonStyle, IconButton, Tooltip};
54use util::ResultExt;
55use workspace::item::Item;
56
57struct SelectionLayout {
58 head: DisplayPoint,
59 cursor_shape: CursorShape,
60 is_newest: bool,
61 is_local: bool,
62 range: Range<DisplayPoint>,
63 active_rows: Range<u32>,
64 user_name: Option<SharedString>,
65}
66
67impl SelectionLayout {
68 fn new<T: ToPoint + ToDisplayPoint + Clone>(
69 selection: Selection<T>,
70 line_mode: bool,
71 cursor_shape: CursorShape,
72 map: &DisplaySnapshot,
73 is_newest: bool,
74 is_local: bool,
75 user_name: Option<SharedString>,
76 ) -> Self {
77 let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
78 let display_selection = point_selection.map(|p| p.to_display_point(map));
79 let mut range = display_selection.range();
80 let mut head = display_selection.head();
81 let mut active_rows = map.prev_line_boundary(point_selection.start).1.row()
82 ..map.next_line_boundary(point_selection.end).1.row();
83
84 // vim visual line mode
85 if line_mode {
86 let point_range = map.expand_to_line(point_selection.range());
87 range = point_range.start.to_display_point(map)..point_range.end.to_display_point(map);
88 }
89
90 // any vim visual mode (including line mode)
91 if cursor_shape == CursorShape::Block && !range.is_empty() && !selection.reversed {
92 if head.column() > 0 {
93 head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left)
94 } else if head.row() > 0 && head != map.max_point() {
95 head = map.clip_point(
96 DisplayPoint::new(head.row() - 1, map.line_len(head.row() - 1)),
97 Bias::Left,
98 );
99 // updating range.end is a no-op unless you're cursor is
100 // on the newline containing a multi-buffer divider
101 // in which case the clip_point may have moved the head up
102 // an additional row.
103 range.end = DisplayPoint::new(head.row() + 1, 0);
104 active_rows.end = head.row();
105 }
106 }
107
108 Self {
109 head,
110 cursor_shape,
111 is_newest,
112 is_local,
113 range,
114 active_rows,
115 user_name,
116 }
117 }
118}
119
120pub struct EditorElement {
121 editor: View<Editor>,
122 style: EditorStyle,
123}
124
125impl EditorElement {
126 pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {
127 Self {
128 editor: editor.clone(),
129 style,
130 }
131 }
132
133 fn register_actions(&self, cx: &mut WindowContext) {
134 let view = &self.editor;
135 view.update(cx, |editor, cx| {
136 for action in editor.editor_actions.iter() {
137 (action)(cx)
138 }
139 });
140
141 crate::rust_analyzer_ext::apply_related_actions(view, cx);
142 register_action(view, cx, Editor::move_left);
143 register_action(view, cx, Editor::move_right);
144 register_action(view, cx, Editor::move_down);
145 register_action(view, cx, Editor::move_down_by_lines);
146 register_action(view, cx, Editor::select_down_by_lines);
147 register_action(view, cx, Editor::move_up);
148 register_action(view, cx, Editor::move_up_by_lines);
149 register_action(view, cx, Editor::select_up_by_lines);
150 register_action(view, cx, Editor::cancel);
151 register_action(view, cx, Editor::newline);
152 register_action(view, cx, Editor::newline_above);
153 register_action(view, cx, Editor::newline_below);
154 register_action(view, cx, Editor::backspace);
155 register_action(view, cx, Editor::delete);
156 register_action(view, cx, Editor::tab);
157 register_action(view, cx, Editor::tab_prev);
158 register_action(view, cx, Editor::indent);
159 register_action(view, cx, Editor::outdent);
160 register_action(view, cx, Editor::delete_line);
161 register_action(view, cx, Editor::join_lines);
162 register_action(view, cx, Editor::sort_lines_case_sensitive);
163 register_action(view, cx, Editor::sort_lines_case_insensitive);
164 register_action(view, cx, Editor::reverse_lines);
165 register_action(view, cx, Editor::shuffle_lines);
166 register_action(view, cx, Editor::convert_to_upper_case);
167 register_action(view, cx, Editor::convert_to_lower_case);
168 register_action(view, cx, Editor::convert_to_title_case);
169 register_action(view, cx, Editor::convert_to_snake_case);
170 register_action(view, cx, Editor::convert_to_kebab_case);
171 register_action(view, cx, Editor::convert_to_upper_camel_case);
172 register_action(view, cx, Editor::convert_to_lower_camel_case);
173 register_action(view, cx, Editor::delete_to_previous_word_start);
174 register_action(view, cx, Editor::delete_to_previous_subword_start);
175 register_action(view, cx, Editor::delete_to_next_word_end);
176 register_action(view, cx, Editor::delete_to_next_subword_end);
177 register_action(view, cx, Editor::delete_to_beginning_of_line);
178 register_action(view, cx, Editor::delete_to_end_of_line);
179 register_action(view, cx, Editor::cut_to_end_of_line);
180 register_action(view, cx, Editor::duplicate_line);
181 register_action(view, cx, Editor::move_line_up);
182 register_action(view, cx, Editor::move_line_down);
183 register_action(view, cx, Editor::transpose);
184 register_action(view, cx, Editor::cut);
185 register_action(view, cx, Editor::copy);
186 register_action(view, cx, Editor::paste);
187 register_action(view, cx, Editor::undo);
188 register_action(view, cx, Editor::redo);
189 register_action(view, cx, Editor::move_page_up);
190 register_action(view, cx, Editor::move_page_down);
191 register_action(view, cx, Editor::next_screen);
192 register_action(view, cx, Editor::scroll_cursor_top);
193 register_action(view, cx, Editor::scroll_cursor_center);
194 register_action(view, cx, Editor::scroll_cursor_bottom);
195 register_action(view, cx, |editor, _: &LineDown, cx| {
196 editor.scroll_screen(&ScrollAmount::Line(1.), cx)
197 });
198 register_action(view, cx, |editor, _: &LineUp, cx| {
199 editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
200 });
201 register_action(view, cx, |editor, _: &HalfPageDown, cx| {
202 editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
203 });
204 register_action(view, cx, |editor, _: &HalfPageUp, cx| {
205 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
206 });
207 register_action(view, cx, |editor, _: &PageDown, cx| {
208 editor.scroll_screen(&ScrollAmount::Page(1.), cx)
209 });
210 register_action(view, cx, |editor, _: &PageUp, cx| {
211 editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
212 });
213 register_action(view, cx, Editor::move_to_previous_word_start);
214 register_action(view, cx, Editor::move_to_previous_subword_start);
215 register_action(view, cx, Editor::move_to_next_word_end);
216 register_action(view, cx, Editor::move_to_next_subword_end);
217 register_action(view, cx, Editor::move_to_beginning_of_line);
218 register_action(view, cx, Editor::move_to_end_of_line);
219 register_action(view, cx, Editor::move_to_start_of_paragraph);
220 register_action(view, cx, Editor::move_to_end_of_paragraph);
221 register_action(view, cx, Editor::move_to_beginning);
222 register_action(view, cx, Editor::move_to_end);
223 register_action(view, cx, Editor::select_up);
224 register_action(view, cx, Editor::select_down);
225 register_action(view, cx, Editor::select_left);
226 register_action(view, cx, Editor::select_right);
227 register_action(view, cx, Editor::select_to_previous_word_start);
228 register_action(view, cx, Editor::select_to_previous_subword_start);
229 register_action(view, cx, Editor::select_to_next_word_end);
230 register_action(view, cx, Editor::select_to_next_subword_end);
231 register_action(view, cx, Editor::select_to_beginning_of_line);
232 register_action(view, cx, Editor::select_to_end_of_line);
233 register_action(view, cx, Editor::select_to_start_of_paragraph);
234 register_action(view, cx, Editor::select_to_end_of_paragraph);
235 register_action(view, cx, Editor::select_to_beginning);
236 register_action(view, cx, Editor::select_to_end);
237 register_action(view, cx, Editor::select_all);
238 register_action(view, cx, |editor, action, cx| {
239 editor.select_all_matches(action, cx).log_err();
240 });
241 register_action(view, cx, Editor::select_line);
242 register_action(view, cx, Editor::split_selection_into_lines);
243 register_action(view, cx, Editor::add_selection_above);
244 register_action(view, cx, Editor::add_selection_below);
245 register_action(view, cx, |editor, action, cx| {
246 editor.select_next(action, cx).log_err();
247 });
248 register_action(view, cx, |editor, action, cx| {
249 editor.select_previous(action, cx).log_err();
250 });
251 register_action(view, cx, Editor::toggle_comments);
252 register_action(view, cx, Editor::select_larger_syntax_node);
253 register_action(view, cx, Editor::select_smaller_syntax_node);
254 register_action(view, cx, Editor::move_to_enclosing_bracket);
255 register_action(view, cx, Editor::undo_selection);
256 register_action(view, cx, Editor::redo_selection);
257 register_action(view, cx, Editor::go_to_diagnostic);
258 register_action(view, cx, Editor::go_to_prev_diagnostic);
259 register_action(view, cx, Editor::go_to_hunk);
260 register_action(view, cx, Editor::go_to_prev_hunk);
261 register_action(view, cx, Editor::go_to_definition);
262 register_action(view, cx, Editor::go_to_definition_split);
263 register_action(view, cx, Editor::go_to_implementation);
264 register_action(view, cx, Editor::go_to_implementation_split);
265 register_action(view, cx, Editor::go_to_type_definition);
266 register_action(view, cx, Editor::go_to_type_definition_split);
267 register_action(view, cx, Editor::open_url);
268 register_action(view, cx, Editor::fold);
269 register_action(view, cx, Editor::fold_at);
270 register_action(view, cx, Editor::unfold_lines);
271 register_action(view, cx, Editor::unfold_at);
272 register_action(view, cx, Editor::fold_selected_ranges);
273 register_action(view, cx, Editor::show_completions);
274 register_action(view, cx, Editor::toggle_code_actions);
275 register_action(view, cx, Editor::open_excerpts);
276 register_action(view, cx, Editor::toggle_soft_wrap);
277 register_action(view, cx, Editor::toggle_inlay_hints);
278 register_action(view, cx, hover_popover::hover);
279 register_action(view, cx, Editor::reveal_in_finder);
280 register_action(view, cx, Editor::copy_path);
281 register_action(view, cx, Editor::copy_relative_path);
282 register_action(view, cx, Editor::copy_highlight_json);
283 register_action(view, cx, Editor::copy_permalink_to_line);
284 register_action(view, cx, Editor::open_permalink_to_line);
285 register_action(view, cx, |editor, action, cx| {
286 if let Some(task) = editor.format(action, cx) {
287 task.detach_and_log_err(cx);
288 } else {
289 cx.propagate();
290 }
291 });
292 register_action(view, cx, Editor::restart_language_server);
293 register_action(view, cx, Editor::show_character_palette);
294 register_action(view, cx, |editor, action, cx| {
295 if let Some(task) = editor.confirm_completion(action, cx) {
296 task.detach_and_log_err(cx);
297 } else {
298 cx.propagate();
299 }
300 });
301 register_action(view, cx, |editor, action, cx| {
302 if let Some(task) = editor.confirm_code_action(action, cx) {
303 task.detach_and_log_err(cx);
304 } else {
305 cx.propagate();
306 }
307 });
308 register_action(view, cx, |editor, action, cx| {
309 if let Some(task) = editor.rename(action, cx) {
310 task.detach_and_log_err(cx);
311 } else {
312 cx.propagate();
313 }
314 });
315 register_action(view, cx, |editor, action, cx| {
316 if let Some(task) = editor.confirm_rename(action, cx) {
317 task.detach_and_log_err(cx);
318 } else {
319 cx.propagate();
320 }
321 });
322 register_action(view, cx, |editor, action, cx| {
323 if let Some(task) = editor.find_all_references(action, cx) {
324 task.detach_and_log_err(cx);
325 } else {
326 cx.propagate();
327 }
328 });
329 register_action(view, cx, Editor::next_copilot_suggestion);
330 register_action(view, cx, Editor::previous_copilot_suggestion);
331 register_action(view, cx, Editor::copilot_suggest);
332 register_action(view, cx, Editor::context_menu_first);
333 register_action(view, cx, Editor::context_menu_prev);
334 register_action(view, cx, Editor::context_menu_next);
335 register_action(view, cx, Editor::context_menu_last);
336 register_action(view, cx, Editor::display_cursor_names);
337 register_action(view, cx, Editor::unique_lines_case_insensitive);
338 register_action(view, cx, Editor::unique_lines_case_sensitive);
339 }
340
341 fn register_key_listeners(
342 &self,
343 cx: &mut ElementContext,
344 text_bounds: Bounds<Pixels>,
345 layout: &LayoutState,
346 ) {
347 let position_map = layout.position_map.clone();
348 let stacking_order = cx.stacking_order().clone();
349 cx.on_key_event({
350 let editor = self.editor.clone();
351 move |event: &ModifiersChangedEvent, phase, cx| {
352 if phase != DispatchPhase::Bubble {
353 return;
354 }
355
356 editor.update(cx, |editor, cx| {
357 Self::modifiers_changed(
358 editor,
359 event,
360 &position_map,
361 text_bounds,
362 &stacking_order,
363 cx,
364 )
365 })
366 }
367 });
368 }
369
370 fn modifiers_changed(
371 editor: &mut Editor,
372 event: &ModifiersChangedEvent,
373 position_map: &PositionMap,
374 text_bounds: Bounds<Pixels>,
375 stacking_order: &StackingOrder,
376 cx: &mut ViewContext<Editor>,
377 ) {
378 let mouse_position = cx.mouse_position();
379 if !text_bounds.contains(&mouse_position)
380 || !cx.was_top_layer(&mouse_position, stacking_order)
381 {
382 return;
383 }
384
385 editor.update_hovered_link(
386 position_map.point_for_position(text_bounds, mouse_position),
387 &position_map.snapshot,
388 event.modifiers,
389 cx,
390 )
391 }
392
393 fn mouse_left_down(
394 editor: &mut Editor,
395 event: &MouseDownEvent,
396 position_map: &PositionMap,
397 text_bounds: Bounds<Pixels>,
398 gutter_bounds: Bounds<Pixels>,
399 stacking_order: &StackingOrder,
400 cx: &mut ViewContext<Editor>,
401 ) {
402 let mut click_count = event.click_count;
403 let modifiers = event.modifiers;
404
405 if cx.default_prevented() {
406 return;
407 } else if gutter_bounds.contains(&event.position) {
408 click_count = 3; // Simulate triple-click when clicking the gutter to select lines
409 } else if !text_bounds.contains(&event.position) {
410 return;
411 }
412 if !cx.was_top_layer(&event.position, stacking_order) {
413 return;
414 }
415
416 let point_for_position = position_map.point_for_position(text_bounds, event.position);
417 let position = point_for_position.previous_valid;
418 if modifiers.shift && modifiers.alt {
419 editor.select(
420 SelectPhase::BeginColumnar {
421 position,
422 goal_column: point_for_position.exact_unclipped.column(),
423 },
424 cx,
425 );
426 } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.command {
427 editor.select(
428 SelectPhase::Extend {
429 position,
430 click_count,
431 },
432 cx,
433 );
434 } else {
435 editor.select(
436 SelectPhase::Begin {
437 position,
438 add: modifiers.alt,
439 click_count,
440 },
441 cx,
442 );
443 }
444
445 cx.stop_propagation();
446 }
447
448 fn mouse_right_down(
449 editor: &mut Editor,
450 event: &MouseDownEvent,
451 position_map: &PositionMap,
452 text_bounds: Bounds<Pixels>,
453 cx: &mut ViewContext<Editor>,
454 ) {
455 if !text_bounds.contains(&event.position) {
456 return;
457 }
458 let point_for_position = position_map.point_for_position(text_bounds, event.position);
459 mouse_context_menu::deploy_context_menu(
460 editor,
461 event.position,
462 point_for_position.previous_valid,
463 cx,
464 );
465 cx.stop_propagation();
466 }
467
468 fn mouse_up(
469 editor: &mut Editor,
470 event: &MouseUpEvent,
471 position_map: &PositionMap,
472 text_bounds: Bounds<Pixels>,
473 interactive_bounds: &InteractiveBounds,
474 stacking_order: &StackingOrder,
475 cx: &mut ViewContext<Editor>,
476 ) {
477 let end_selection = editor.has_pending_selection();
478 let pending_nonempty_selections = editor.has_pending_nonempty_selection();
479
480 if end_selection {
481 editor.select(SelectPhase::End, cx);
482 }
483
484 if interactive_bounds.visibly_contains(&event.position, cx)
485 && !pending_nonempty_selections
486 && event.modifiers.command
487 && text_bounds.contains(&event.position)
488 && cx.was_top_layer(&event.position, stacking_order)
489 {
490 let point = position_map.point_for_position(text_bounds, event.position);
491 editor.handle_click_hovered_link(point, event.modifiers, cx);
492
493 cx.stop_propagation();
494 } else if end_selection {
495 cx.stop_propagation();
496 }
497 }
498
499 fn mouse_dragged(
500 editor: &mut Editor,
501 event: &MouseMoveEvent,
502 position_map: &PositionMap,
503 text_bounds: Bounds<Pixels>,
504 _gutter_bounds: Bounds<Pixels>,
505 _stacking_order: &StackingOrder,
506 cx: &mut ViewContext<Editor>,
507 ) {
508 if !editor.has_pending_selection() {
509 return;
510 }
511
512 let point_for_position = position_map.point_for_position(text_bounds, event.position);
513 let mut scroll_delta = gpui::Point::<f32>::default();
514 let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
515 let top = text_bounds.origin.y + vertical_margin;
516 let bottom = text_bounds.lower_left().y - vertical_margin;
517 if event.position.y < top {
518 scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y);
519 }
520 if event.position.y > bottom {
521 scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
522 }
523
524 let horizontal_margin = position_map.line_height.min(text_bounds.size.width / 3.0);
525 let left = text_bounds.origin.x + horizontal_margin;
526 let right = text_bounds.upper_right().x - horizontal_margin;
527 if event.position.x < left {
528 scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
529 }
530 if event.position.x > right {
531 scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
532 }
533
534 editor.select(
535 SelectPhase::Update {
536 position: point_for_position.previous_valid,
537 goal_column: point_for_position.exact_unclipped.column(),
538 scroll_delta,
539 },
540 cx,
541 );
542 }
543
544 fn mouse_moved(
545 editor: &mut Editor,
546 event: &MouseMoveEvent,
547 position_map: &PositionMap,
548 text_bounds: Bounds<Pixels>,
549 gutter_bounds: Bounds<Pixels>,
550 stacking_order: &StackingOrder,
551 cx: &mut ViewContext<Editor>,
552 ) {
553 let modifiers = event.modifiers;
554 let text_hovered = text_bounds.contains(&event.position);
555 let gutter_hovered = gutter_bounds.contains(&event.position);
556 let was_top = cx.was_top_layer(&event.position, stacking_order);
557
558 editor.set_gutter_hovered(gutter_hovered, cx);
559
560 // Don't trigger hover popover if mouse is hovering over context menu
561 if text_hovered && was_top {
562 let point_for_position = position_map.point_for_position(text_bounds, event.position);
563
564 editor.update_hovered_link(point_for_position, &position_map.snapshot, modifiers, cx);
565
566 if let Some(point) = point_for_position.as_valid() {
567 hover_at(editor, Some(point), cx);
568 Self::update_visible_cursor(editor, point, position_map, cx);
569 }
570 } else {
571 editor.hide_hovered_link(cx);
572 hover_at(editor, None, cx);
573 if gutter_hovered && was_top {
574 cx.stop_propagation();
575 }
576 }
577 }
578
579 fn update_visible_cursor(
580 editor: &mut Editor,
581 point: DisplayPoint,
582 position_map: &PositionMap,
583 cx: &mut ViewContext<Editor>,
584 ) {
585 let snapshot = &position_map.snapshot;
586 let Some(hub) = editor.collaboration_hub() else {
587 return;
588 };
589 let range = DisplayPoint::new(point.row(), point.column().saturating_sub(1))
590 ..DisplayPoint::new(
591 point.row(),
592 (point.column() + 1).min(snapshot.line_len(point.row())),
593 );
594
595 let range = snapshot
596 .buffer_snapshot
597 .anchor_at(range.start.to_point(&snapshot.display_snapshot), Bias::Left)
598 ..snapshot
599 .buffer_snapshot
600 .anchor_at(range.end.to_point(&snapshot.display_snapshot), Bias::Right);
601
602 let Some(selection) = snapshot.remote_selections_in_range(&range, hub, cx).next() else {
603 return;
604 };
605 let key = crate::HoveredCursor {
606 replica_id: selection.replica_id,
607 selection_id: selection.selection.id,
608 };
609 editor.hovered_cursors.insert(
610 key.clone(),
611 cx.spawn(|editor, mut cx| async move {
612 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
613 editor
614 .update(&mut cx, |editor, cx| {
615 editor.hovered_cursors.remove(&key);
616 cx.notify();
617 })
618 .ok();
619 }),
620 );
621 cx.notify()
622 }
623
624 fn paint_background(
625 &self,
626 gutter_bounds: Bounds<Pixels>,
627 text_bounds: Bounds<Pixels>,
628 layout: &LayoutState,
629 cx: &mut ElementContext,
630 ) {
631 let bounds = gutter_bounds.union(&text_bounds);
632 let scroll_top =
633 layout.position_map.snapshot.scroll_position().y * layout.position_map.line_height;
634 let gutter_bg = cx.theme().colors().editor_gutter_background;
635 cx.paint_quad(fill(gutter_bounds, gutter_bg));
636 cx.paint_quad(fill(text_bounds, self.style.background));
637
638 if let EditorMode::Full = layout.mode {
639 let mut active_rows = layout.active_rows.iter().peekable();
640 while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
641 let mut end_row = *start_row;
642 while active_rows.peek().map_or(false, |r| {
643 *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
644 }) {
645 active_rows.next().unwrap();
646 end_row += 1;
647 }
648
649 if !contains_non_empty_selection {
650 let origin = point(
651 bounds.origin.x,
652 bounds.origin.y + (layout.position_map.line_height * *start_row as f32)
653 - scroll_top,
654 );
655 let size = size(
656 bounds.size.width,
657 layout.position_map.line_height * (end_row - start_row + 1) as f32,
658 );
659 let active_line_bg = cx.theme().colors().editor_active_line_background;
660 cx.paint_quad(fill(Bounds { origin, size }, active_line_bg));
661 }
662 }
663
664 if let Some(highlighted_rows) = &layout.highlighted_rows {
665 let origin = point(
666 bounds.origin.x,
667 bounds.origin.y
668 + (layout.position_map.line_height * highlighted_rows.start as f32)
669 - scroll_top,
670 );
671 let size = size(
672 bounds.size.width,
673 layout.position_map.line_height * highlighted_rows.len() as f32,
674 );
675 let highlighted_line_bg = cx.theme().colors().editor_highlighted_line_background;
676 cx.paint_quad(fill(Bounds { origin, size }, highlighted_line_bg));
677 }
678
679 let scroll_left =
680 layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
681
682 for (wrap_position, active) in layout.wrap_guides.iter() {
683 let x = (text_bounds.origin.x + *wrap_position + layout.position_map.em_width / 2.)
684 - scroll_left;
685
686 if x < text_bounds.origin.x
687 || (layout.show_scrollbars && x > self.scrollbar_left(&bounds))
688 {
689 continue;
690 }
691
692 let color = if *active {
693 cx.theme().colors().editor_active_wrap_guide
694 } else {
695 cx.theme().colors().editor_wrap_guide
696 };
697 cx.paint_quad(fill(
698 Bounds {
699 origin: point(x, text_bounds.origin.y),
700 size: size(px(1.), text_bounds.size.height),
701 },
702 color,
703 ));
704 }
705 }
706 }
707
708 fn paint_gutter(
709 &mut self,
710 bounds: Bounds<Pixels>,
711 layout: &mut LayoutState,
712 cx: &mut ElementContext,
713 ) {
714 let line_height = layout.position_map.line_height;
715
716 let scroll_position = layout.position_map.snapshot.scroll_position();
717 let scroll_top = scroll_position.y * line_height;
718
719 let show_gutter = matches!(
720 ProjectSettings::get_global(cx).git.git_gutter,
721 Some(GitGutterSetting::TrackedFiles)
722 );
723
724 if show_gutter {
725 Self::paint_diff_hunks(bounds, layout, cx);
726 }
727
728 for (ix, line) in layout.line_numbers.iter().enumerate() {
729 if let Some(line) = line {
730 let line_origin = bounds.origin
731 + point(
732 bounds.size.width - line.width - layout.gutter_padding,
733 ix as f32 * line_height - (scroll_top % line_height),
734 );
735
736 line.paint(line_origin, line_height, cx).log_err();
737 }
738 }
739
740 cx.with_z_index(1, |cx| {
741 for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() {
742 if let Some(fold_indicator) = fold_indicator {
743 let mut fold_indicator = fold_indicator.into_any_element();
744 let available_space = size(
745 AvailableSpace::MinContent,
746 AvailableSpace::Definite(line_height * 0.55),
747 );
748 let fold_indicator_size = fold_indicator.measure(available_space, cx);
749
750 let position = point(
751 bounds.size.width - layout.gutter_padding,
752 ix as f32 * line_height - (scroll_top % line_height),
753 );
754 let centering_offset = point(
755 (layout.gutter_padding + layout.gutter_margin - fold_indicator_size.width)
756 / 2.,
757 (line_height - fold_indicator_size.height) / 2.,
758 );
759 let origin = bounds.origin + position + centering_offset;
760 fold_indicator.draw(origin, available_space, cx);
761 }
762 }
763
764 if let Some(indicator) = layout.code_actions_indicator.take() {
765 let mut button = indicator.button.into_any_element();
766 let available_space = size(
767 AvailableSpace::MinContent,
768 AvailableSpace::Definite(line_height),
769 );
770 let indicator_size = button.measure(available_space, cx);
771
772 let mut x = Pixels::ZERO;
773 let mut y = indicator.row as f32 * line_height - scroll_top;
774 // Center indicator.
775 x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.;
776 y += (line_height - indicator_size.height) / 2.;
777
778 button.draw(bounds.origin + point(x, y), available_space, cx);
779 }
780 });
781 }
782
783 fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut ElementContext) {
784 let line_height = layout.position_map.line_height;
785
786 let scroll_position = layout.position_map.snapshot.scroll_position();
787 let scroll_top = scroll_position.y * line_height;
788
789 for hunk in &layout.display_hunks {
790 let (display_row_range, status) = match hunk {
791 //TODO: This rendering is entirely a horrible hack
792 &DisplayDiffHunk::Folded { display_row: row } => {
793 let start_y = row as f32 * line_height - scroll_top;
794 let end_y = start_y + line_height;
795
796 let width = 0.275 * line_height;
797 let highlight_origin = bounds.origin + point(-width, start_y);
798 let highlight_size = size(width * 2., end_y - start_y);
799 let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
800 cx.paint_quad(quad(
801 highlight_bounds,
802 Corners::all(1. * line_height),
803 cx.theme().status().modified,
804 Edges::default(),
805 transparent_black(),
806 ));
807
808 continue;
809 }
810
811 DisplayDiffHunk::Unfolded {
812 display_row_range,
813 status,
814 } => (display_row_range, status),
815 };
816
817 let color = match status {
818 DiffHunkStatus::Added => cx.theme().status().created,
819 DiffHunkStatus::Modified => cx.theme().status().modified,
820
821 //TODO: This rendering is entirely a horrible hack
822 DiffHunkStatus::Removed => {
823 let row = display_row_range.start;
824
825 let offset = line_height / 2.;
826 let start_y = row as f32 * line_height - offset - scroll_top;
827 let end_y = start_y + line_height;
828
829 let width = 0.275 * line_height;
830 let highlight_origin = bounds.origin + point(-width, start_y);
831 let highlight_size = size(width * 2., end_y - start_y);
832 let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
833 cx.paint_quad(quad(
834 highlight_bounds,
835 Corners::all(1. * line_height),
836 cx.theme().status().deleted,
837 Edges::default(),
838 transparent_black(),
839 ));
840
841 continue;
842 }
843 };
844
845 let start_row = display_row_range.start;
846 let end_row = display_row_range.end;
847 // If we're in a multibuffer, row range span might include an
848 // excerpt header, so if we were to draw the marker straight away,
849 // the hunk might include the rows of that header.
850 // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap.
851 // Instead, we simply check whether the range we're dealing with includes
852 // any excerpt headers and if so, we stop painting the diff hunk on the first row of that header.
853 let end_row_in_current_excerpt = layout
854 .position_map
855 .snapshot
856 .blocks_in_range(start_row..end_row)
857 .find_map(|(start_row, block)| {
858 if matches!(block, TransformBlock::ExcerptHeader { .. }) {
859 Some(start_row)
860 } else {
861 None
862 }
863 })
864 .unwrap_or(end_row);
865
866 let start_y = start_row as f32 * line_height - scroll_top;
867 let end_y = end_row_in_current_excerpt as f32 * line_height - scroll_top;
868
869 let width = 0.275 * line_height;
870 let highlight_origin = bounds.origin + point(-width, start_y);
871 let highlight_size = size(width * 2., end_y - start_y);
872 let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
873 cx.paint_quad(quad(
874 highlight_bounds,
875 Corners::all(0.05 * line_height),
876 color,
877 Edges::default(),
878 transparent_black(),
879 ));
880 }
881 }
882
883 fn paint_text(
884 &mut self,
885 text_bounds: Bounds<Pixels>,
886 layout: &mut LayoutState,
887 cx: &mut ElementContext,
888 ) {
889 let start_row = layout.visible_display_row_range.start;
890 let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
891 let line_end_overshoot = 0.15 * layout.position_map.line_height;
892 let whitespace_setting = self
893 .editor
894 .read(cx)
895 .buffer
896 .read(cx)
897 .settings_at(0, cx)
898 .show_whitespaces;
899
900 cx.with_content_mask(
901 Some(ContentMask {
902 bounds: text_bounds,
903 }),
904 |cx| {
905 let interactive_text_bounds = InteractiveBounds {
906 bounds: text_bounds,
907 stacking_order: cx.stacking_order().clone(),
908 };
909 if interactive_text_bounds.visibly_contains(&cx.mouse_position(), cx) {
910 if self
911 .editor
912 .read(cx)
913 .hovered_link_state
914 .as_ref()
915 .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
916 {
917 cx.set_cursor_style(CursorStyle::PointingHand);
918 } else {
919 cx.set_cursor_style(CursorStyle::IBeam);
920 }
921 }
922
923 let fold_corner_radius = 0.15 * layout.position_map.line_height;
924 cx.with_element_id(Some("folds"), |cx| {
925 let snapshot = &layout.position_map.snapshot;
926
927 for fold in snapshot.folds_in_range(layout.visible_anchor_range.clone()) {
928 let fold_range = fold.range.clone();
929 let display_range = fold.range.start.to_display_point(&snapshot)
930 ..fold.range.end.to_display_point(&snapshot);
931 debug_assert_eq!(display_range.start.row(), display_range.end.row());
932 let row = display_range.start.row();
933 debug_assert!(row < layout.visible_display_row_range.end);
934 let Some(line_layout) = &layout
935 .position_map
936 .line_layouts
937 .get((row - layout.visible_display_row_range.start) as usize)
938 .map(|l| &l.line)
939 else {
940 continue;
941 };
942
943 let start_x = content_origin.x
944 + line_layout.x_for_index(display_range.start.column() as usize)
945 - layout.position_map.scroll_position.x;
946 let start_y = content_origin.y
947 + row as f32 * layout.position_map.line_height
948 - layout.position_map.scroll_position.y;
949 let end_x = content_origin.x
950 + line_layout.x_for_index(display_range.end.column() as usize)
951 - layout.position_map.scroll_position.x;
952
953 let fold_bounds = Bounds {
954 origin: point(start_x, start_y),
955 size: size(end_x - start_x, layout.position_map.line_height),
956 };
957
958 let fold_background = cx.with_z_index(1, |cx| {
959 div()
960 .id(fold.id)
961 .size_full()
962 .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
963 .on_click(cx.listener_for(
964 &self.editor,
965 move |editor: &mut Editor, _, cx| {
966 editor.unfold_ranges(
967 [fold_range.start..fold_range.end],
968 true,
969 false,
970 cx,
971 );
972 cx.stop_propagation();
973 },
974 ))
975 .draw_and_update_state(
976 fold_bounds.origin,
977 fold_bounds.size,
978 cx,
979 |fold_element_state, cx| {
980 if fold_element_state.is_active() {
981 cx.theme().colors().ghost_element_active
982 } else if fold_bounds.contains(&cx.mouse_position()) {
983 cx.theme().colors().ghost_element_hover
984 } else {
985 cx.theme().colors().ghost_element_background
986 }
987 },
988 )
989 });
990
991 self.paint_highlighted_range(
992 display_range.clone(),
993 fold_background,
994 fold_corner_radius,
995 fold_corner_radius * 2.,
996 layout,
997 content_origin,
998 text_bounds,
999 cx,
1000 );
1001 }
1002 });
1003
1004 for (range, color) in &layout.highlighted_ranges {
1005 self.paint_highlighted_range(
1006 range.clone(),
1007 *color,
1008 Pixels::ZERO,
1009 line_end_overshoot,
1010 layout,
1011 content_origin,
1012 text_bounds,
1013 cx,
1014 );
1015 }
1016
1017 let mut cursors = SmallVec::<[Cursor; 32]>::new();
1018 let corner_radius = 0.15 * layout.position_map.line_height;
1019 let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
1020
1021 for (participant_ix, (player_color, selections)) in
1022 layout.selections.iter().enumerate()
1023 {
1024 for selection in selections.into_iter() {
1025 self.paint_highlighted_range(
1026 selection.range.clone(),
1027 player_color.selection,
1028 corner_radius,
1029 corner_radius * 2.,
1030 layout,
1031 content_origin,
1032 text_bounds,
1033 cx,
1034 );
1035
1036 if selection.is_local && !selection.range.is_empty() {
1037 invisible_display_ranges.push(selection.range.clone());
1038 }
1039
1040 if !selection.is_local || self.editor.read(cx).show_local_cursors(cx) {
1041 let cursor_position = selection.head;
1042 if layout
1043 .visible_display_row_range
1044 .contains(&cursor_position.row())
1045 {
1046 let cursor_row_layout = &layout.position_map.line_layouts
1047 [(cursor_position.row() - start_row) as usize]
1048 .line;
1049 let cursor_column = cursor_position.column() as usize;
1050
1051 let cursor_character_x =
1052 cursor_row_layout.x_for_index(cursor_column);
1053 let mut block_width = cursor_row_layout
1054 .x_for_index(cursor_column + 1)
1055 - cursor_character_x;
1056 if block_width == Pixels::ZERO {
1057 block_width = layout.position_map.em_width;
1058 }
1059 let block_text = if let CursorShape::Block = selection.cursor_shape
1060 {
1061 layout
1062 .position_map
1063 .snapshot
1064 .chars_at(cursor_position)
1065 .next()
1066 .and_then(|(character, _)| {
1067 let text = if character == '\n' {
1068 SharedString::from(" ")
1069 } else {
1070 SharedString::from(character.to_string())
1071 };
1072 let len = text.len();
1073 cx.text_system()
1074 .shape_line(
1075 text,
1076 cursor_row_layout.font_size,
1077 &[TextRun {
1078 len,
1079 font: self.style.text.font(),
1080 color: self.style.background,
1081 background_color: None,
1082 strikethrough: None,
1083 underline: None,
1084 }],
1085 )
1086 .log_err()
1087 })
1088 } else {
1089 None
1090 };
1091
1092 let x = cursor_character_x - layout.position_map.scroll_position.x;
1093 let y = cursor_position.row() as f32
1094 * layout.position_map.line_height
1095 - layout.position_map.scroll_position.y;
1096 if selection.is_newest {
1097 self.editor.update(cx, |editor, _| {
1098 editor.pixel_position_of_newest_cursor = Some(point(
1099 text_bounds.origin.x + x + block_width / 2.,
1100 text_bounds.origin.y
1101 + y
1102 + layout.position_map.line_height / 2.,
1103 ))
1104 });
1105 }
1106
1107 cursors.push(Cursor {
1108 color: player_color.cursor,
1109 block_width,
1110 origin: point(x, y),
1111 line_height: layout.position_map.line_height,
1112 shape: selection.cursor_shape,
1113 block_text,
1114 cursor_name: selection.user_name.clone().map(|name| {
1115 CursorName {
1116 string: name,
1117 color: self.style.background,
1118 is_top_row: cursor_position.row() == 0,
1119 z_index: (participant_ix % 256).try_into().unwrap(),
1120 }
1121 }),
1122 });
1123 }
1124 }
1125 }
1126 }
1127
1128 for (ix, line_with_invisibles) in
1129 layout.position_map.line_layouts.iter().enumerate()
1130 {
1131 let row = start_row + ix as u32;
1132 line_with_invisibles.draw(
1133 layout,
1134 row,
1135 content_origin,
1136 whitespace_setting,
1137 &invisible_display_ranges,
1138 cx,
1139 )
1140 }
1141
1142 cx.with_z_index(0, |cx| self.paint_redactions(text_bounds, &layout, cx));
1143
1144 cx.with_z_index(1, |cx| {
1145 for cursor in cursors {
1146 cursor.paint(content_origin, cx);
1147 }
1148 });
1149 },
1150 )
1151 }
1152
1153 fn paint_redactions(
1154 &mut self,
1155 text_bounds: Bounds<Pixels>,
1156 layout: &LayoutState,
1157 cx: &mut ElementContext,
1158 ) {
1159 let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
1160 let line_end_overshoot = layout.line_end_overshoot();
1161
1162 // A softer than perfect black
1163 let redaction_color = gpui::rgb(0x0e1111);
1164
1165 for range in layout.redacted_ranges.iter() {
1166 self.paint_highlighted_range(
1167 range.clone(),
1168 redaction_color.into(),
1169 Pixels::ZERO,
1170 line_end_overshoot,
1171 layout,
1172 content_origin,
1173 text_bounds,
1174 cx,
1175 );
1176 }
1177 }
1178
1179 fn paint_overlays(
1180 &mut self,
1181 text_bounds: Bounds<Pixels>,
1182 layout: &mut LayoutState,
1183 cx: &mut ElementContext,
1184 ) {
1185 let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
1186 let start_row = layout.visible_display_row_range.start;
1187 if let Some((position, mut context_menu)) = layout.context_menu.take() {
1188 let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
1189 let context_menu_size = context_menu.measure(available_space, cx);
1190
1191 let cursor_row_layout =
1192 &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
1193 let x = cursor_row_layout.x_for_index(position.column() as usize)
1194 - layout.position_map.scroll_position.x;
1195 let y = (position.row() + 1) as f32 * layout.position_map.line_height
1196 - layout.position_map.scroll_position.y;
1197 let mut list_origin = content_origin + point(x, y);
1198 let list_width = context_menu_size.width;
1199 let list_height = context_menu_size.height;
1200
1201 // Snap the right edge of the list to the right edge of the window if
1202 // its horizontal bounds overflow.
1203 if list_origin.x + list_width > cx.viewport_size().width {
1204 list_origin.x = (cx.viewport_size().width - list_width).max(Pixels::ZERO);
1205 }
1206
1207 if list_origin.y + list_height > text_bounds.lower_right().y {
1208 list_origin.y -= layout.position_map.line_height + list_height;
1209 }
1210
1211 cx.break_content_mask(|cx| context_menu.draw(list_origin, available_space, cx));
1212 }
1213
1214 if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() {
1215 let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
1216
1217 // This is safe because we check on layout whether the required row is available
1218 let hovered_row_layout =
1219 &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
1220
1221 // Minimum required size: Take the first popover, and add 1.5 times the minimum popover
1222 // height. This is the size we will use to decide whether to render popovers above or below
1223 // the hovered line.
1224 let first_size = hover_popovers[0].measure(available_space, cx);
1225 let height_to_reserve =
1226 first_size.height + 1.5 * MIN_POPOVER_LINE_HEIGHT * layout.position_map.line_height;
1227
1228 // Compute Hovered Point
1229 let x = hovered_row_layout.x_for_index(position.column() as usize)
1230 - layout.position_map.scroll_position.x;
1231 let y = position.row() as f32 * layout.position_map.line_height
1232 - layout.position_map.scroll_position.y;
1233 let hovered_point = content_origin + point(x, y);
1234
1235 if hovered_point.y - height_to_reserve > Pixels::ZERO {
1236 // There is enough space above. Render popovers above the hovered point
1237 let mut current_y = hovered_point.y;
1238 for mut hover_popover in hover_popovers {
1239 let size = hover_popover.measure(available_space, cx);
1240 let mut popover_origin = point(hovered_point.x, current_y - size.height);
1241
1242 let x_out_of_bounds =
1243 text_bounds.upper_right().x - (popover_origin.x + size.width);
1244 if x_out_of_bounds < Pixels::ZERO {
1245 popover_origin.x = popover_origin.x + x_out_of_bounds;
1246 }
1247
1248 if cx.was_top_layer(&popover_origin, cx.stacking_order()) {
1249 cx.break_content_mask(|cx| {
1250 hover_popover.draw(popover_origin, available_space, cx)
1251 });
1252 }
1253
1254 current_y = popover_origin.y - HOVER_POPOVER_GAP;
1255 }
1256 } else {
1257 // There is not enough space above. Render popovers below the hovered point
1258 let mut current_y = hovered_point.y + layout.position_map.line_height;
1259 for mut hover_popover in hover_popovers {
1260 let size = hover_popover.measure(available_space, cx);
1261 let mut popover_origin = point(hovered_point.x, current_y);
1262
1263 let x_out_of_bounds =
1264 text_bounds.upper_right().x - (popover_origin.x + size.width);
1265 if x_out_of_bounds < Pixels::ZERO {
1266 popover_origin.x = popover_origin.x + x_out_of_bounds;
1267 }
1268
1269 hover_popover.draw(popover_origin, available_space, cx);
1270
1271 current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
1272 }
1273 }
1274 }
1275
1276 if let Some(mouse_context_menu) = self.editor.read(cx).mouse_context_menu.as_ref() {
1277 let element = overlay()
1278 .position(mouse_context_menu.position)
1279 .child(mouse_context_menu.context_menu.clone())
1280 .anchor(AnchorCorner::TopLeft)
1281 .snap_to_window();
1282 element.into_any().draw(
1283 gpui::Point::default(),
1284 size(AvailableSpace::MinContent, AvailableSpace::MinContent),
1285 cx,
1286 );
1287 }
1288 }
1289
1290 fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
1291 bounds.upper_right().x - self.style.scrollbar_width
1292 }
1293
1294 fn paint_scrollbar(
1295 &mut self,
1296 bounds: Bounds<Pixels>,
1297 layout: &mut LayoutState,
1298 cx: &mut ElementContext,
1299 ) {
1300 if layout.mode != EditorMode::Full {
1301 return;
1302 }
1303
1304 // If a drag took place after we started dragging the scrollbar,
1305 // cancel the scrollbar drag.
1306 if cx.has_active_drag() {
1307 self.editor.update(cx, |editor, cx| {
1308 editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
1309 });
1310 }
1311
1312 let top = bounds.origin.y;
1313 let bottom = bounds.lower_left().y;
1314 let right = bounds.lower_right().x;
1315 let left = self.scrollbar_left(&bounds);
1316 let row_range = layout.scrollbar_row_range.clone();
1317 let max_row = layout.max_row as f32 + (row_range.end - row_range.start);
1318
1319 let mut height = bounds.size.height;
1320 let mut first_row_y_offset = px(0.0);
1321
1322 // Impose a minimum height on the scrollbar thumb
1323 let row_height = height / max_row;
1324 let min_thumb_height = layout.position_map.line_height;
1325 let thumb_height = (row_range.end - row_range.start) * row_height;
1326 if thumb_height < min_thumb_height {
1327 first_row_y_offset = (min_thumb_height - thumb_height) / 2.0;
1328 height -= min_thumb_height - thumb_height;
1329 }
1330
1331 let y_for_row = |row: f32| -> Pixels { top + first_row_y_offset + row * row_height };
1332
1333 let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
1334 let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
1335 let track_bounds = Bounds::from_corners(point(left, top), point(right, bottom));
1336 let thumb_bounds = Bounds::from_corners(point(left, thumb_top), point(right, thumb_bottom));
1337
1338 if layout.show_scrollbars {
1339 cx.paint_quad(quad(
1340 track_bounds,
1341 Corners::default(),
1342 cx.theme().colors().scrollbar_track_background,
1343 Edges {
1344 top: Pixels::ZERO,
1345 right: Pixels::ZERO,
1346 bottom: Pixels::ZERO,
1347 left: px(1.),
1348 },
1349 cx.theme().colors().scrollbar_track_border,
1350 ));
1351 let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
1352 if layout.is_singleton && scrollbar_settings.selections {
1353 let start_anchor = Anchor::min();
1354 let end_anchor = Anchor::max();
1355 let background_ranges = self
1356 .editor
1357 .read(cx)
1358 .background_highlight_row_ranges::<BufferSearchHighlights>(
1359 start_anchor..end_anchor,
1360 &layout.position_map.snapshot,
1361 50000,
1362 );
1363 for range in background_ranges {
1364 let start_y = y_for_row(range.start().row() as f32);
1365 let mut end_y = y_for_row(range.end().row() as f32);
1366 if end_y - start_y < px(1.) {
1367 end_y = start_y + px(1.);
1368 }
1369 let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
1370 cx.paint_quad(quad(
1371 bounds,
1372 Corners::default(),
1373 cx.theme().status().info,
1374 Edges {
1375 top: Pixels::ZERO,
1376 right: px(1.),
1377 bottom: Pixels::ZERO,
1378 left: px(1.),
1379 },
1380 cx.theme().colors().scrollbar_thumb_border,
1381 ));
1382 }
1383 }
1384
1385 if layout.is_singleton && scrollbar_settings.symbols_selections {
1386 let selection_ranges = self.editor.read(cx).background_highlights_in_range(
1387 Anchor::min()..Anchor::max(),
1388 &layout.position_map.snapshot,
1389 cx.theme().colors(),
1390 );
1391 for hunk in selection_ranges {
1392 let start_display = Point::new(hunk.0.start.row(), 0)
1393 .to_display_point(&layout.position_map.snapshot.display_snapshot);
1394 let end_display = Point::new(hunk.0.end.row(), 0)
1395 .to_display_point(&layout.position_map.snapshot.display_snapshot);
1396 let start_y = y_for_row(start_display.row() as f32);
1397 let mut end_y = if hunk.0.start == hunk.0.end {
1398 y_for_row((end_display.row() + 1) as f32)
1399 } else {
1400 y_for_row((end_display.row()) as f32)
1401 };
1402
1403 if end_y - start_y < px(1.) {
1404 end_y = start_y + px(1.);
1405 }
1406 let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
1407
1408 cx.paint_quad(quad(
1409 bounds,
1410 Corners::default(),
1411 cx.theme().status().info,
1412 Edges {
1413 top: Pixels::ZERO,
1414 right: px(1.),
1415 bottom: Pixels::ZERO,
1416 left: px(1.),
1417 },
1418 cx.theme().colors().scrollbar_thumb_border,
1419 ));
1420 }
1421 }
1422
1423 if layout.is_singleton && scrollbar_settings.git_diff {
1424 for hunk in layout
1425 .position_map
1426 .snapshot
1427 .buffer_snapshot
1428 .git_diff_hunks_in_range(0..(max_row.floor() as u32))
1429 {
1430 let start_display = Point::new(hunk.buffer_range.start, 0)
1431 .to_display_point(&layout.position_map.snapshot.display_snapshot);
1432 let end_display = Point::new(hunk.buffer_range.end, 0)
1433 .to_display_point(&layout.position_map.snapshot.display_snapshot);
1434 let start_y = y_for_row(start_display.row() as f32);
1435 let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
1436 y_for_row((end_display.row() + 1) as f32)
1437 } else {
1438 y_for_row((end_display.row()) as f32)
1439 };
1440
1441 if end_y - start_y < px(1.) {
1442 end_y = start_y + px(1.);
1443 }
1444 let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
1445
1446 let color = match hunk.status() {
1447 DiffHunkStatus::Added => cx.theme().status().created,
1448 DiffHunkStatus::Modified => cx.theme().status().modified,
1449 DiffHunkStatus::Removed => cx.theme().status().deleted,
1450 };
1451 cx.paint_quad(quad(
1452 bounds,
1453 Corners::default(),
1454 color,
1455 Edges {
1456 top: Pixels::ZERO,
1457 right: px(1.),
1458 bottom: Pixels::ZERO,
1459 left: px(1.),
1460 },
1461 cx.theme().colors().scrollbar_thumb_border,
1462 ));
1463 }
1464 }
1465
1466 if layout.is_singleton && scrollbar_settings.diagnostics {
1467 let max_point = layout
1468 .position_map
1469 .snapshot
1470 .display_snapshot
1471 .buffer_snapshot
1472 .max_point();
1473
1474 let diagnostics = layout
1475 .position_map
1476 .snapshot
1477 .buffer_snapshot
1478 .diagnostics_in_range::<_, Point>(Point::zero()..max_point, false)
1479 // We want to sort by severity, in order to paint the most severe diagnostics last.
1480 .sorted_by_key(|diagnostic| std::cmp::Reverse(diagnostic.diagnostic.severity));
1481
1482 for diagnostic in diagnostics {
1483 let start_display = diagnostic
1484 .range
1485 .start
1486 .to_display_point(&layout.position_map.snapshot.display_snapshot);
1487 let end_display = diagnostic
1488 .range
1489 .end
1490 .to_display_point(&layout.position_map.snapshot.display_snapshot);
1491 let start_y = y_for_row(start_display.row() as f32);
1492 let mut end_y = if diagnostic.range.start == diagnostic.range.end {
1493 y_for_row((end_display.row() + 1) as f32)
1494 } else {
1495 y_for_row((end_display.row()) as f32)
1496 };
1497
1498 if end_y - start_y < px(1.) {
1499 end_y = start_y + px(1.);
1500 }
1501 let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
1502
1503 let color = match diagnostic.diagnostic.severity {
1504 DiagnosticSeverity::ERROR => cx.theme().status().error,
1505 DiagnosticSeverity::WARNING => cx.theme().status().warning,
1506 DiagnosticSeverity::INFORMATION => cx.theme().status().info,
1507 _ => cx.theme().status().hint,
1508 };
1509 cx.paint_quad(quad(
1510 bounds,
1511 Corners::default(),
1512 color,
1513 Edges {
1514 top: Pixels::ZERO,
1515 right: px(1.),
1516 bottom: Pixels::ZERO,
1517 left: px(1.),
1518 },
1519 cx.theme().colors().scrollbar_thumb_border,
1520 ));
1521 }
1522 }
1523
1524 cx.paint_quad(quad(
1525 thumb_bounds,
1526 Corners::default(),
1527 cx.theme().colors().scrollbar_thumb_background,
1528 Edges {
1529 top: Pixels::ZERO,
1530 right: px(1.),
1531 bottom: Pixels::ZERO,
1532 left: px(1.),
1533 },
1534 cx.theme().colors().scrollbar_thumb_border,
1535 ));
1536 }
1537
1538 let interactive_track_bounds = InteractiveBounds {
1539 bounds: track_bounds,
1540 stacking_order: cx.stacking_order().clone(),
1541 };
1542 let mut mouse_position = cx.mouse_position();
1543 if interactive_track_bounds.visibly_contains(&mouse_position, cx) {
1544 cx.set_cursor_style(CursorStyle::Arrow);
1545 }
1546
1547 cx.on_mouse_event({
1548 let editor = self.editor.clone();
1549 move |event: &MouseMoveEvent, phase, cx| {
1550 if phase == DispatchPhase::Capture {
1551 return;
1552 }
1553
1554 editor.update(cx, |editor, cx| {
1555 if event.pressed_button == Some(MouseButton::Left)
1556 && editor.scroll_manager.is_dragging_scrollbar()
1557 {
1558 let y = mouse_position.y;
1559 let new_y = event.position.y;
1560 if (track_bounds.top()..track_bounds.bottom()).contains(&y) {
1561 let mut position = editor.scroll_position(cx);
1562 position.y += (new_y - y) * (max_row as f32) / height;
1563 if position.y < 0.0 {
1564 position.y = 0.0;
1565 }
1566 editor.set_scroll_position(position, cx);
1567 }
1568
1569 mouse_position = event.position;
1570 cx.stop_propagation();
1571 } else {
1572 editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
1573 if interactive_track_bounds.visibly_contains(&event.position, cx) {
1574 editor.scroll_manager.show_scrollbar(cx);
1575 }
1576 }
1577 })
1578 }
1579 });
1580
1581 if self.editor.read(cx).scroll_manager.is_dragging_scrollbar() {
1582 cx.on_mouse_event({
1583 let editor = self.editor.clone();
1584 move |_: &MouseUpEvent, phase, cx| {
1585 if phase == DispatchPhase::Capture {
1586 return;
1587 }
1588
1589 editor.update(cx, |editor, cx| {
1590 editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
1591 cx.stop_propagation();
1592 });
1593 }
1594 });
1595 } else {
1596 cx.on_mouse_event({
1597 let editor = self.editor.clone();
1598 move |event: &MouseDownEvent, phase, cx| {
1599 if phase == DispatchPhase::Capture {
1600 return;
1601 }
1602
1603 editor.update(cx, |editor, cx| {
1604 if track_bounds.contains(&event.position) {
1605 editor.scroll_manager.set_is_dragging_scrollbar(true, cx);
1606
1607 let y = event.position.y;
1608 if y < thumb_top || thumb_bottom < y {
1609 let center_row =
1610 ((y - top) * max_row as f32 / height).round() as u32;
1611 let top_row = center_row
1612 .saturating_sub((row_range.end - row_range.start) as u32 / 2);
1613 let mut position = editor.scroll_position(cx);
1614 position.y = top_row as f32;
1615 editor.set_scroll_position(position, cx);
1616 } else {
1617 editor.scroll_manager.show_scrollbar(cx);
1618 }
1619
1620 cx.stop_propagation();
1621 }
1622 });
1623 }
1624 });
1625 }
1626 }
1627
1628 #[allow(clippy::too_many_arguments)]
1629 fn paint_highlighted_range(
1630 &self,
1631 range: Range<DisplayPoint>,
1632 color: Hsla,
1633 corner_radius: Pixels,
1634 line_end_overshoot: Pixels,
1635 layout: &LayoutState,
1636 content_origin: gpui::Point<Pixels>,
1637 bounds: Bounds<Pixels>,
1638 cx: &mut ElementContext,
1639 ) {
1640 let start_row = layout.visible_display_row_range.start;
1641 let end_row = layout.visible_display_row_range.end;
1642 if range.start != range.end {
1643 let row_range = if range.end.column() == 0 {
1644 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
1645 } else {
1646 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
1647 };
1648
1649 let highlighted_range = HighlightedRange {
1650 color,
1651 line_height: layout.position_map.line_height,
1652 corner_radius,
1653 start_y: content_origin.y
1654 + row_range.start as f32 * layout.position_map.line_height
1655 - layout.position_map.scroll_position.y,
1656 lines: row_range
1657 .into_iter()
1658 .map(|row| {
1659 let line_layout =
1660 &layout.position_map.line_layouts[(row - start_row) as usize].line;
1661 HighlightedRangeLine {
1662 start_x: if row == range.start.row() {
1663 content_origin.x
1664 + line_layout.x_for_index(range.start.column() as usize)
1665 - layout.position_map.scroll_position.x
1666 } else {
1667 content_origin.x - layout.position_map.scroll_position.x
1668 },
1669 end_x: if row == range.end.row() {
1670 content_origin.x
1671 + line_layout.x_for_index(range.end.column() as usize)
1672 - layout.position_map.scroll_position.x
1673 } else {
1674 content_origin.x + line_layout.width + line_end_overshoot
1675 - layout.position_map.scroll_position.x
1676 },
1677 }
1678 })
1679 .collect(),
1680 };
1681
1682 highlighted_range.paint(bounds, cx);
1683 }
1684 }
1685
1686 fn paint_blocks(
1687 &mut self,
1688 bounds: Bounds<Pixels>,
1689 layout: &mut LayoutState,
1690 cx: &mut ElementContext,
1691 ) {
1692 let scroll_position = layout.position_map.snapshot.scroll_position();
1693 let scroll_left = scroll_position.x * layout.position_map.em_width;
1694 let scroll_top = scroll_position.y * layout.position_map.line_height;
1695
1696 for mut block in layout.blocks.drain(..) {
1697 let mut origin = bounds.origin
1698 + point(
1699 Pixels::ZERO,
1700 block.row as f32 * layout.position_map.line_height - scroll_top,
1701 );
1702 if !matches!(block.style, BlockStyle::Sticky) {
1703 origin += point(-scroll_left, Pixels::ZERO);
1704 }
1705 block.element.draw(origin, block.available_space, cx);
1706 }
1707 }
1708
1709 fn column_pixels(&self, column: usize, cx: &WindowContext) -> Pixels {
1710 let style = &self.style;
1711 let font_size = style.text.font_size.to_pixels(cx.rem_size());
1712 let layout = cx
1713 .text_system()
1714 .shape_line(
1715 SharedString::from(" ".repeat(column)),
1716 font_size,
1717 &[TextRun {
1718 len: column,
1719 font: style.text.font(),
1720 color: Hsla::default(),
1721 background_color: None,
1722 underline: None,
1723 strikethrough: None,
1724 }],
1725 )
1726 .unwrap();
1727
1728 layout.width
1729 }
1730
1731 fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &WindowContext) -> Pixels {
1732 let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
1733 self.column_pixels(digit_count, cx)
1734 }
1735
1736 //Folds contained in a hunk are ignored apart from shrinking visual size
1737 //If a fold contains any hunks then that fold line is marked as modified
1738 fn layout_git_gutters(
1739 &self,
1740 display_rows: Range<u32>,
1741 snapshot: &EditorSnapshot,
1742 ) -> Vec<DisplayDiffHunk> {
1743 let buffer_snapshot = &snapshot.buffer_snapshot;
1744
1745 let buffer_start_row = DisplayPoint::new(display_rows.start, 0)
1746 .to_point(snapshot)
1747 .row;
1748 let buffer_end_row = DisplayPoint::new(display_rows.end, 0)
1749 .to_point(snapshot)
1750 .row;
1751
1752 buffer_snapshot
1753 .git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
1754 .map(|hunk| diff_hunk_to_display(hunk, snapshot))
1755 .dedup()
1756 .collect()
1757 }
1758
1759 fn calculate_relative_line_numbers(
1760 &self,
1761 snapshot: &EditorSnapshot,
1762 rows: &Range<u32>,
1763 relative_to: Option<u32>,
1764 ) -> HashMap<u32, u32> {
1765 let mut relative_rows: HashMap<u32, u32> = Default::default();
1766 let Some(relative_to) = relative_to else {
1767 return relative_rows;
1768 };
1769
1770 let start = rows.start.min(relative_to);
1771 let end = rows.end.max(relative_to);
1772
1773 let buffer_rows = snapshot
1774 .buffer_rows(start)
1775 .take(1 + (end - start) as usize)
1776 .collect::<Vec<_>>();
1777
1778 let head_idx = relative_to - start;
1779 let mut delta = 1;
1780 let mut i = head_idx + 1;
1781 while i < buffer_rows.len() as u32 {
1782 if buffer_rows[i as usize].is_some() {
1783 if rows.contains(&(i + start)) {
1784 relative_rows.insert(i + start, delta);
1785 }
1786 delta += 1;
1787 }
1788 i += 1;
1789 }
1790 delta = 1;
1791 i = head_idx.min(buffer_rows.len() as u32 - 1);
1792 while i > 0 && buffer_rows[i as usize].is_none() {
1793 i -= 1;
1794 }
1795
1796 while i > 0 {
1797 i -= 1;
1798 if buffer_rows[i as usize].is_some() {
1799 if rows.contains(&(i + start)) {
1800 relative_rows.insert(i + start, delta);
1801 }
1802 delta += 1;
1803 }
1804 }
1805
1806 relative_rows
1807 }
1808
1809 fn shape_line_numbers(
1810 &self,
1811 rows: Range<u32>,
1812 active_rows: &BTreeMap<u32, bool>,
1813 newest_selection_head: DisplayPoint,
1814 is_singleton: bool,
1815 snapshot: &EditorSnapshot,
1816 cx: &ViewContext<Editor>,
1817 ) -> (
1818 Vec<Option<ShapedLine>>,
1819 Vec<Option<(FoldStatus, BufferRow, bool)>>,
1820 ) {
1821 let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
1822 let include_line_numbers = snapshot.mode == EditorMode::Full;
1823 let mut shaped_line_numbers = Vec::with_capacity(rows.len());
1824 let mut fold_statuses = Vec::with_capacity(rows.len());
1825 let mut line_number = String::new();
1826 let is_relative = EditorSettings::get_global(cx).relative_line_numbers;
1827 let relative_to = if is_relative {
1828 Some(newest_selection_head.row())
1829 } else {
1830 None
1831 };
1832
1833 let relative_rows = self.calculate_relative_line_numbers(&snapshot, &rows, relative_to);
1834
1835 for (ix, row) in snapshot
1836 .buffer_rows(rows.start)
1837 .take((rows.end - rows.start) as usize)
1838 .enumerate()
1839 {
1840 let display_row = rows.start + ix as u32;
1841 let (active, color) = if active_rows.contains_key(&display_row) {
1842 (true, cx.theme().colors().editor_active_line_number)
1843 } else {
1844 (false, cx.theme().colors().editor_line_number)
1845 };
1846 if let Some(buffer_row) = row {
1847 if include_line_numbers {
1848 line_number.clear();
1849 let default_number = buffer_row + 1;
1850 let number = relative_rows
1851 .get(&(ix as u32 + rows.start))
1852 .unwrap_or(&default_number);
1853 write!(&mut line_number, "{}", number).unwrap();
1854 let run = TextRun {
1855 len: line_number.len(),
1856 font: self.style.text.font(),
1857 color,
1858 background_color: None,
1859 underline: None,
1860 strikethrough: None,
1861 };
1862 let shaped_line = cx
1863 .text_system()
1864 .shape_line(line_number.clone().into(), font_size, &[run])
1865 .unwrap();
1866 shaped_line_numbers.push(Some(shaped_line));
1867 fold_statuses.push(
1868 is_singleton
1869 .then(|| {
1870 snapshot
1871 .fold_for_line(buffer_row)
1872 .map(|fold_status| (fold_status, buffer_row, active))
1873 })
1874 .flatten(),
1875 )
1876 }
1877 } else {
1878 fold_statuses.push(None);
1879 shaped_line_numbers.push(None);
1880 }
1881 }
1882
1883 (shaped_line_numbers, fold_statuses)
1884 }
1885
1886 fn layout_lines(
1887 &self,
1888 rows: Range<u32>,
1889 line_number_layouts: &[Option<ShapedLine>],
1890 snapshot: &EditorSnapshot,
1891 cx: &ViewContext<Editor>,
1892 ) -> Vec<LineWithInvisibles> {
1893 if rows.start >= rows.end {
1894 return Vec::new();
1895 }
1896
1897 // Show the placeholder when the editor is empty
1898 if snapshot.is_empty() {
1899 let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
1900 let placeholder_color = cx.theme().colors().text_placeholder;
1901 let placeholder_text = snapshot.placeholder_text();
1902
1903 let placeholder_lines = placeholder_text
1904 .as_ref()
1905 .map_or("", AsRef::as_ref)
1906 .split('\n')
1907 .skip(rows.start as usize)
1908 .chain(iter::repeat(""))
1909 .take(rows.len());
1910 placeholder_lines
1911 .filter_map(move |line| {
1912 let run = TextRun {
1913 len: line.len(),
1914 font: self.style.text.font(),
1915 color: placeholder_color,
1916 background_color: None,
1917 underline: Default::default(),
1918 strikethrough: None,
1919 };
1920 cx.text_system()
1921 .shape_line(line.to_string().into(), font_size, &[run])
1922 .log_err()
1923 })
1924 .map(|line| LineWithInvisibles {
1925 line,
1926 invisibles: Vec::new(),
1927 })
1928 .collect()
1929 } else {
1930 let chunks = snapshot.highlighted_chunks(rows.clone(), true, &self.style);
1931 LineWithInvisibles::from_chunks(
1932 chunks,
1933 &self.style.text,
1934 MAX_LINE_LEN,
1935 rows.len() as usize,
1936 line_number_layouts,
1937 snapshot.mode,
1938 cx,
1939 )
1940 }
1941 }
1942
1943 fn compute_layout(&mut self, bounds: Bounds<Pixels>, cx: &mut ElementContext) -> LayoutState {
1944 self.editor.update(cx, |editor, cx| {
1945 let snapshot = editor.snapshot(cx);
1946 let style = self.style.clone();
1947
1948 let font_id = cx.text_system().resolve_font(&style.text.font());
1949 let font_size = style.text.font_size.to_pixels(cx.rem_size());
1950 let line_height = style.text.line_height_in_pixels(cx.rem_size());
1951 let em_width = cx
1952 .text_system()
1953 .typographic_bounds(font_id, font_size, 'm')
1954 .unwrap()
1955 .size
1956 .width;
1957 let em_advance = cx
1958 .text_system()
1959 .advance(font_id, font_size, 'm')
1960 .unwrap()
1961 .width;
1962
1963 let gutter_dimensions = snapshot.gutter_dimensions(font_id, font_size, em_width, self.max_line_number_width(&snapshot, cx), cx);
1964
1965 editor.gutter_width = gutter_dimensions.width;
1966
1967 let text_width = bounds.size.width - gutter_dimensions.width;
1968 let overscroll = size(em_width, px(0.));
1969 let _snapshot = {
1970 editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
1971
1972 let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width;
1973 let wrap_width = match editor.soft_wrap_mode(cx) {
1974 SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
1975 SoftWrap::EditorWidth => editor_width,
1976 SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
1977 };
1978
1979 if editor.set_wrap_width(Some(wrap_width), cx) {
1980 editor.snapshot(cx)
1981 } else {
1982 snapshot
1983 }
1984 };
1985
1986 let wrap_guides = editor
1987 .wrap_guides(cx)
1988 .iter()
1989 .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
1990 .collect::<SmallVec<[_; 2]>>();
1991
1992 let gutter_size = size(gutter_dimensions.width, bounds.size.height);
1993 let text_size = size(text_width, bounds.size.height);
1994
1995 let autoscroll_horizontally =
1996 editor.autoscroll_vertically(bounds.size.height, line_height, cx);
1997 let mut snapshot = editor.snapshot(cx);
1998
1999 let scroll_position = snapshot.scroll_position();
2000 // The scroll position is a fractional point, the whole number of which represents
2001 // the top of the window in terms of display rows.
2002 let start_row = scroll_position.y as u32;
2003 let height_in_lines = f32::from(bounds.size.height / line_height);
2004 let max_row = snapshot.max_point().row();
2005
2006 // Add 1 to ensure selections bleed off screen
2007 let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row);
2008
2009 let start_anchor = if start_row == 0 {
2010 Anchor::min()
2011 } else {
2012 snapshot
2013 .buffer_snapshot
2014 .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
2015 };
2016 let end_anchor = if end_row > max_row {
2017 Anchor::max()
2018 } else {
2019 snapshot
2020 .buffer_snapshot
2021 .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
2022 };
2023
2024 let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
2025 let mut active_rows = BTreeMap::new();
2026 let is_singleton = editor.is_singleton(cx);
2027
2028 let highlighted_rows = editor.highlighted_rows();
2029 let highlighted_ranges = editor.background_highlights_in_range(
2030 start_anchor..end_anchor,
2031 &snapshot.display_snapshot,
2032 cx.theme().colors(),
2033 );
2034
2035 let redacted_ranges = editor.redacted_ranges(start_anchor..end_anchor, &snapshot.display_snapshot, cx);
2036
2037 let mut newest_selection_head = None;
2038
2039 if editor.show_local_selections {
2040 let mut local_selections: Vec<Selection<Point>> = editor
2041 .selections
2042 .disjoint_in_range(start_anchor..end_anchor, cx);
2043 local_selections.extend(editor.selections.pending(cx));
2044 let mut layouts = Vec::new();
2045 let newest = editor.selections.newest(cx);
2046 for selection in local_selections.drain(..) {
2047 let is_empty = selection.start == selection.end;
2048 let is_newest = selection == newest;
2049
2050 let layout = SelectionLayout::new(
2051 selection,
2052 editor.selections.line_mode,
2053 editor.cursor_shape,
2054 &snapshot.display_snapshot,
2055 is_newest,
2056 true,
2057 None,
2058 );
2059 if is_newest {
2060 newest_selection_head = Some(layout.head);
2061 }
2062
2063 for row in cmp::max(layout.active_rows.start, start_row)
2064 ..=cmp::min(layout.active_rows.end, end_row)
2065 {
2066 let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
2067 *contains_non_empty_selection |= !is_empty;
2068 }
2069 layouts.push(layout);
2070 }
2071
2072 let player = if editor.read_only(cx) {
2073 cx.theme().players().read_only()
2074 } else {
2075 style.local_player
2076 };
2077
2078 selections.push((player, layouts));
2079 }
2080
2081 if let Some(collaboration_hub) = &editor.collaboration_hub {
2082 // When following someone, render the local selections in their color.
2083 if let Some(leader_id) = editor.leader_peer_id {
2084 if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
2085 if let Some(participant_index) = collaboration_hub
2086 .user_participant_indices(cx)
2087 .get(&collaborator.user_id)
2088 {
2089 if let Some((local_selection_style, _)) = selections.first_mut() {
2090 *local_selection_style = cx
2091 .theme()
2092 .players()
2093 .color_for_participant(participant_index.0);
2094 }
2095 }
2096 }
2097 }
2098
2099 let mut remote_selections = HashMap::default();
2100 for selection in snapshot.remote_selections_in_range(
2101 &(start_anchor..end_anchor),
2102 collaboration_hub.as_ref(),
2103 cx,
2104 ) {
2105 let selection_style = if let Some(participant_index) = selection.participant_index {
2106 cx.theme()
2107 .players()
2108 .color_for_participant(participant_index.0)
2109 } else {
2110 cx.theme().players().absent()
2111 };
2112
2113 // Don't re-render the leader's selections, since the local selections
2114 // match theirs.
2115 if Some(selection.peer_id) == editor.leader_peer_id {
2116 continue;
2117 }
2118 let key = HoveredCursor{replica_id: selection.replica_id, selection_id: selection.selection.id};
2119
2120 let is_shown = editor.show_cursor_names || editor.hovered_cursors.contains_key(&key);
2121
2122 remote_selections
2123 .entry(selection.replica_id)
2124 .or_insert((selection_style, Vec::new()))
2125 .1
2126 .push(SelectionLayout::new(
2127 selection.selection,
2128 selection.line_mode,
2129 selection.cursor_shape,
2130 &snapshot.display_snapshot,
2131 false,
2132 false,
2133 if is_shown {
2134 selection.user_name
2135 } else {
2136 None
2137 },
2138 ));
2139 }
2140
2141 selections.extend(remote_selections.into_values());
2142 }
2143
2144 let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
2145 let show_scrollbars = match scrollbar_settings.show {
2146 ShowScrollbar::Auto => {
2147 // Git
2148 (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
2149 ||
2150 // Selections
2151 (is_singleton && scrollbar_settings.selections && editor.has_background_highlights::<BufferSearchHighlights>())
2152 ||
2153 // Symbols Selections
2154 (is_singleton && scrollbar_settings.symbols_selections && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
2155 ||
2156 // Diagnostics
2157 (is_singleton && scrollbar_settings.diagnostics && snapshot.buffer_snapshot.has_diagnostics())
2158 ||
2159 // Scrollmanager
2160 editor.scroll_manager.scrollbars_visible()
2161 }
2162 ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
2163 ShowScrollbar::Always => true,
2164 ShowScrollbar::Never => false,
2165 };
2166
2167 let head_for_relative = newest_selection_head.unwrap_or_else(|| {
2168 let newest = editor.selections.newest::<Point>(cx);
2169 SelectionLayout::new(
2170 newest,
2171 editor.selections.line_mode,
2172 editor.cursor_shape,
2173 &snapshot.display_snapshot,
2174 true,
2175 true,
2176 None,
2177 )
2178 .head
2179 });
2180
2181 let (line_numbers, fold_statuses) = self.shape_line_numbers(
2182 start_row..end_row,
2183 &active_rows,
2184 head_for_relative,
2185 is_singleton,
2186 &snapshot,
2187 cx,
2188 );
2189
2190 let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
2191
2192 let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
2193
2194 let mut max_visible_line_width = Pixels::ZERO;
2195 let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
2196 for line_with_invisibles in &line_layouts {
2197 if line_with_invisibles.line.width > max_visible_line_width {
2198 max_visible_line_width = line_with_invisibles.line.width;
2199 }
2200 }
2201
2202 let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
2203 .unwrap()
2204 .width;
2205 let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
2206
2207 let editor_view = cx.view().clone();
2208 let (scroll_width, blocks) = cx.with_element_context(|cx| {
2209 cx.with_element_id(Some("editor_blocks"), |cx| {
2210 self.layout_blocks(
2211 start_row..end_row,
2212 &snapshot,
2213 bounds.size.width,
2214 scroll_width,
2215 text_width,
2216 gutter_dimensions.padding,
2217 gutter_dimensions.width,
2218 em_width,
2219 gutter_dimensions.width + gutter_dimensions.margin,
2220 line_height,
2221 &style,
2222 &line_layouts,
2223 editor,
2224 editor_view,
2225 cx,
2226 )
2227 })
2228 });
2229
2230 let scroll_max = point(
2231 f32::from((scroll_width - text_size.width) / em_width).max(0.0),
2232 max_row as f32,
2233 );
2234
2235 let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
2236
2237 let autoscrolled = if autoscroll_horizontally {
2238 editor.autoscroll_horizontally(
2239 start_row,
2240 text_size.width,
2241 scroll_width,
2242 em_width,
2243 &line_layouts,
2244 cx,
2245 )
2246 } else {
2247 false
2248 };
2249
2250 if clamped || autoscrolled {
2251 snapshot = editor.snapshot(cx);
2252 }
2253
2254 let mut context_menu = None;
2255 let mut code_actions_indicator = None;
2256 if let Some(newest_selection_head) = newest_selection_head {
2257 if (start_row..end_row).contains(&newest_selection_head.row()) {
2258 if editor.context_menu_visible() {
2259 let max_height = cmp::min(
2260 12. * line_height,
2261 cmp::max(
2262 3. * line_height,
2263 (bounds.size.height - line_height) / 2.,
2264 )
2265 );
2266 context_menu =
2267 editor.render_context_menu(newest_selection_head, &self.style, max_height, cx);
2268 }
2269
2270 let active = matches!(
2271 editor.context_menu.read().as_ref(),
2272 Some(crate::ContextMenu::CodeActions(_))
2273 );
2274
2275 code_actions_indicator = editor
2276 .render_code_actions_indicator(&style, active, cx)
2277 .map(|element| CodeActionsIndicator {
2278 row: newest_selection_head.row(),
2279 button: element,
2280 });
2281 }
2282 }
2283
2284 let visible_rows = start_row..start_row + line_layouts.len() as u32;
2285 let max_size = size(
2286 (120. * em_width) // Default size
2287 .min(bounds.size.width / 2.) // Shrink to half of the editor width
2288 .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
2289 (16. * line_height) // Default size
2290 .min(bounds.size.height / 2.) // Shrink to half of the editor height
2291 .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
2292 );
2293
2294 let hover = if context_menu.is_some() {
2295 None
2296 } else {
2297 editor.hover_state.render(
2298 &snapshot,
2299 &style,
2300 visible_rows,
2301 max_size,
2302 editor.workspace.as_ref().map(|(w, _)| w.clone()),
2303 cx,
2304 )
2305 };
2306
2307 let editor_view = cx.view().clone();
2308 let fold_indicators = cx.with_element_context(|cx| {
2309
2310 cx.with_element_id(Some("gutter_fold_indicators"), |_cx| {
2311 editor.render_fold_indicators(
2312 fold_statuses,
2313 &style,
2314 editor.gutter_hovered,
2315 line_height,
2316 gutter_dimensions.margin,
2317 editor_view,
2318 )
2319 })
2320 });
2321
2322 let invisible_symbol_font_size = font_size / 2.;
2323 let tab_invisible = cx
2324 .text_system()
2325 .shape_line(
2326 "→".into(),
2327 invisible_symbol_font_size,
2328 &[TextRun {
2329 len: "→".len(),
2330 font: self.style.text.font(),
2331 color: cx.theme().colors().editor_invisible,
2332 background_color: None,
2333 underline: None,
2334 strikethrough: None,
2335 }],
2336 )
2337 .unwrap();
2338 let space_invisible = cx
2339 .text_system()
2340 .shape_line(
2341 "•".into(),
2342 invisible_symbol_font_size,
2343 &[TextRun {
2344 len: "•".len(),
2345 font: self.style.text.font(),
2346 color: cx.theme().colors().editor_invisible,
2347 background_color: None,
2348 underline: None,
2349 strikethrough: None,
2350 }],
2351 )
2352 .unwrap();
2353
2354 LayoutState {
2355 mode: snapshot.mode,
2356 position_map: Arc::new(PositionMap {
2357 size: bounds.size,
2358 scroll_position: point(
2359 scroll_position.x * em_width,
2360 scroll_position.y * line_height,
2361 ),
2362 scroll_max,
2363 line_layouts,
2364 line_height,
2365 em_width,
2366 em_advance,
2367 snapshot,
2368 }),
2369 visible_anchor_range: start_anchor..end_anchor,
2370 visible_display_row_range: start_row..end_row,
2371 wrap_guides,
2372 gutter_size,
2373 gutter_padding: gutter_dimensions.padding,
2374 text_size,
2375 scrollbar_row_range,
2376 show_scrollbars,
2377 is_singleton,
2378 max_row,
2379 gutter_margin: gutter_dimensions.margin,
2380 active_rows,
2381 highlighted_rows,
2382 highlighted_ranges,
2383 redacted_ranges,
2384 line_numbers,
2385 display_hunks,
2386 blocks,
2387 selections,
2388 context_menu,
2389 code_actions_indicator,
2390 fold_indicators,
2391 tab_invisible,
2392 space_invisible,
2393 hover_popovers: hover,
2394 }
2395 })
2396 }
2397
2398 #[allow(clippy::too_many_arguments)]
2399 fn layout_blocks(
2400 &self,
2401 rows: Range<u32>,
2402 snapshot: &EditorSnapshot,
2403 editor_width: Pixels,
2404 scroll_width: Pixels,
2405 text_width: Pixels,
2406 gutter_padding: Pixels,
2407 gutter_width: Pixels,
2408 em_width: Pixels,
2409 text_x: Pixels,
2410 line_height: Pixels,
2411 style: &EditorStyle,
2412 line_layouts: &[LineWithInvisibles],
2413 editor: &mut Editor,
2414 editor_view: View<Editor>,
2415 cx: &mut ElementContext,
2416 ) -> (Pixels, Vec<BlockLayout>) {
2417 let mut block_id = 0;
2418 let (fixed_blocks, non_fixed_blocks) = snapshot
2419 .blocks_in_range(rows.clone())
2420 .partition::<Vec<_>, _>(|(_, block)| match block {
2421 TransformBlock::ExcerptHeader { .. } => false,
2422 TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
2423 });
2424
2425 let render_block = |block: &TransformBlock,
2426 available_space: Size<AvailableSpace>,
2427 block_id: usize,
2428 editor: &mut Editor,
2429 cx: &mut ElementContext| {
2430 let mut element = match block {
2431 TransformBlock::Custom(block) => {
2432 let align_to = block
2433 .position()
2434 .to_point(&snapshot.buffer_snapshot)
2435 .to_display_point(snapshot);
2436 let anchor_x = text_x
2437 + if rows.contains(&align_to.row()) {
2438 line_layouts[(align_to.row() - rows.start) as usize]
2439 .line
2440 .x_for_index(align_to.column() as usize)
2441 } else {
2442 layout_line(align_to.row(), snapshot, style, cx)
2443 .unwrap()
2444 .x_for_index(align_to.column() as usize)
2445 };
2446
2447 block.render(&mut BlockContext {
2448 context: cx,
2449 anchor_x,
2450 gutter_padding,
2451 line_height,
2452 gutter_width,
2453 em_width,
2454 block_id,
2455 max_width: scroll_width.max(text_width),
2456 view: editor_view.clone(),
2457 editor_style: &self.style,
2458 })
2459 }
2460
2461 TransformBlock::ExcerptHeader {
2462 buffer,
2463 range,
2464 starts_new_buffer,
2465 ..
2466 } => {
2467 let include_root = editor
2468 .project
2469 .as_ref()
2470 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
2471 .unwrap_or_default();
2472
2473 let jump_handler = project::File::from_dyn(buffer.file()).map(|file| {
2474 let jump_path = ProjectPath {
2475 worktree_id: file.worktree_id(cx),
2476 path: file.path.clone(),
2477 };
2478 let jump_anchor = range
2479 .primary
2480 .as_ref()
2481 .map_or(range.context.start, |primary| primary.start);
2482 let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
2483
2484 cx.listener_for(&self.editor, move |editor, _, cx| {
2485 editor.jump(jump_path.clone(), jump_position, jump_anchor, cx);
2486 })
2487 });
2488
2489 let element = if *starts_new_buffer {
2490 let path = buffer.resolve_file_path(cx, include_root);
2491 let mut filename = None;
2492 let mut parent_path = None;
2493 // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
2494 if let Some(path) = path {
2495 filename = path.file_name().map(|f| f.to_string_lossy().to_string());
2496 parent_path = path
2497 .parent()
2498 .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
2499 }
2500
2501 v_flex()
2502 .id(("path header container", block_id))
2503 .size_full()
2504 .justify_center()
2505 .p(gpui::px(6.))
2506 .child(
2507 h_flex()
2508 .id("path header block")
2509 .size_full()
2510 .pl(gpui::px(12.))
2511 .pr(gpui::px(8.))
2512 .rounded_md()
2513 .shadow_md()
2514 .border()
2515 .border_color(cx.theme().colors().border)
2516 .bg(cx.theme().colors().editor_subheader_background)
2517 .justify_between()
2518 .hover(|style| style.bg(cx.theme().colors().element_hover))
2519 .child(
2520 h_flex().gap_3().child(
2521 h_flex()
2522 .gap_2()
2523 .child(
2524 filename
2525 .map(SharedString::from)
2526 .unwrap_or_else(|| "untitled".into()),
2527 )
2528 .when_some(parent_path, |then, path| {
2529 then.child(
2530 div().child(path).text_color(
2531 cx.theme().colors().text_muted,
2532 ),
2533 )
2534 }),
2535 ),
2536 )
2537 .when_some(jump_handler, |this, jump_handler| {
2538 this.cursor_pointer()
2539 .tooltip(|cx| {
2540 Tooltip::for_action(
2541 "Jump to Buffer",
2542 &OpenExcerpts,
2543 cx,
2544 )
2545 })
2546 .on_mouse_down(MouseButton::Left, |_, cx| {
2547 cx.stop_propagation()
2548 })
2549 .on_click(jump_handler)
2550 }),
2551 )
2552 } else {
2553 h_flex()
2554 .id(("collapsed context", block_id))
2555 .size_full()
2556 .gap(gutter_padding)
2557 .child(
2558 h_flex()
2559 .justify_end()
2560 .flex_none()
2561 .w(gutter_width - gutter_padding)
2562 .h_full()
2563 .text_buffer(cx)
2564 .text_color(cx.theme().colors().editor_line_number)
2565 .child("..."),
2566 )
2567 .child(
2568 ButtonLike::new("jump to collapsed context")
2569 .style(ButtonStyle::Transparent)
2570 .full_width()
2571 .child(
2572 div()
2573 .h_px()
2574 .w_full()
2575 .bg(cx.theme().colors().border_variant)
2576 .group_hover("", |style| {
2577 style.bg(cx.theme().colors().border)
2578 }),
2579 )
2580 .when_some(jump_handler, |this, jump_handler| {
2581 this.on_click(jump_handler).tooltip(|cx| {
2582 Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx)
2583 })
2584 }),
2585 )
2586 };
2587 element.into_any()
2588 }
2589 };
2590
2591 let size = element.measure(available_space, cx);
2592 (element, size)
2593 };
2594
2595 let mut fixed_block_max_width = Pixels::ZERO;
2596 let mut blocks = Vec::new();
2597 for (row, block) in fixed_blocks {
2598 let available_space = size(
2599 AvailableSpace::MinContent,
2600 AvailableSpace::Definite(block.height() as f32 * line_height),
2601 );
2602 let (element, element_size) =
2603 render_block(block, available_space, block_id, editor, cx);
2604 block_id += 1;
2605 fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
2606 blocks.push(BlockLayout {
2607 row,
2608 element,
2609 available_space,
2610 style: BlockStyle::Fixed,
2611 });
2612 }
2613 for (row, block) in non_fixed_blocks {
2614 let style = match block {
2615 TransformBlock::Custom(block) => block.style(),
2616 TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
2617 };
2618 let width = match style {
2619 BlockStyle::Sticky => editor_width,
2620 BlockStyle::Flex => editor_width
2621 .max(fixed_block_max_width)
2622 .max(gutter_width + scroll_width),
2623 BlockStyle::Fixed => unreachable!(),
2624 };
2625 let available_space = size(
2626 AvailableSpace::Definite(width),
2627 AvailableSpace::Definite(block.height() as f32 * line_height),
2628 );
2629 let (element, _) = render_block(block, available_space, block_id, editor, cx);
2630 block_id += 1;
2631 blocks.push(BlockLayout {
2632 row,
2633 element,
2634 available_space,
2635 style,
2636 });
2637 }
2638 (
2639 scroll_width.max(fixed_block_max_width - gutter_width),
2640 blocks,
2641 )
2642 }
2643
2644 fn paint_scroll_wheel_listener(
2645 &mut self,
2646 interactive_bounds: &InteractiveBounds,
2647 layout: &LayoutState,
2648 cx: &mut ElementContext,
2649 ) {
2650 cx.on_mouse_event({
2651 let position_map = layout.position_map.clone();
2652 let editor = self.editor.clone();
2653 let interactive_bounds = interactive_bounds.clone();
2654 let mut delta = ScrollDelta::default();
2655
2656 move |event: &ScrollWheelEvent, phase, cx| {
2657 if phase == DispatchPhase::Bubble
2658 && interactive_bounds.visibly_contains(&event.position, cx)
2659 {
2660 delta = delta.coalesce(event.delta);
2661 editor.update(cx, |editor, cx| {
2662 let position = event.position;
2663 let position_map: &PositionMap = &position_map;
2664 let bounds = &interactive_bounds;
2665 if !bounds.visibly_contains(&position, cx) {
2666 return;
2667 }
2668
2669 let line_height = position_map.line_height;
2670 let max_glyph_width = position_map.em_width;
2671 let (delta, axis) = match delta {
2672 gpui::ScrollDelta::Pixels(mut pixels) => {
2673 //Trackpad
2674 let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
2675 (pixels, axis)
2676 }
2677
2678 gpui::ScrollDelta::Lines(lines) => {
2679 //Not trackpad
2680 let pixels =
2681 point(lines.x * max_glyph_width, lines.y * line_height);
2682 (pixels, None)
2683 }
2684 };
2685
2686 let scroll_position = position_map.snapshot.scroll_position();
2687 let x = f32::from(
2688 (scroll_position.x * max_glyph_width - delta.x) / max_glyph_width,
2689 );
2690 let y =
2691 f32::from((scroll_position.y * line_height - delta.y) / line_height);
2692 let scroll_position =
2693 point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
2694 editor.scroll(scroll_position, axis, cx);
2695 cx.stop_propagation();
2696 });
2697 }
2698 }
2699 });
2700 }
2701
2702 fn paint_mouse_listeners(
2703 &mut self,
2704 bounds: Bounds<Pixels>,
2705 gutter_bounds: Bounds<Pixels>,
2706 text_bounds: Bounds<Pixels>,
2707 layout: &LayoutState,
2708 cx: &mut ElementContext,
2709 ) {
2710 let interactive_bounds = InteractiveBounds {
2711 bounds: bounds.intersect(&cx.content_mask().bounds),
2712 stacking_order: cx.stacking_order().clone(),
2713 };
2714
2715 self.paint_scroll_wheel_listener(&interactive_bounds, layout, cx);
2716
2717 cx.on_mouse_event({
2718 let position_map = layout.position_map.clone();
2719 let editor = self.editor.clone();
2720 let stacking_order = cx.stacking_order().clone();
2721 let interactive_bounds = interactive_bounds.clone();
2722
2723 move |event: &MouseDownEvent, phase, cx| {
2724 if phase == DispatchPhase::Bubble
2725 && interactive_bounds.visibly_contains(&event.position, cx)
2726 {
2727 match event.button {
2728 MouseButton::Left => editor.update(cx, |editor, cx| {
2729 Self::mouse_left_down(
2730 editor,
2731 event,
2732 &position_map,
2733 text_bounds,
2734 gutter_bounds,
2735 &stacking_order,
2736 cx,
2737 );
2738 }),
2739 MouseButton::Right => editor.update(cx, |editor, cx| {
2740 Self::mouse_right_down(editor, event, &position_map, text_bounds, cx);
2741 }),
2742 _ => {}
2743 };
2744 }
2745 }
2746 });
2747
2748 cx.on_mouse_event({
2749 let position_map = layout.position_map.clone();
2750 let editor = self.editor.clone();
2751 let stacking_order = cx.stacking_order().clone();
2752 let interactive_bounds = interactive_bounds.clone();
2753
2754 move |event: &MouseUpEvent, phase, cx| {
2755 if phase == DispatchPhase::Bubble {
2756 editor.update(cx, |editor, cx| {
2757 Self::mouse_up(
2758 editor,
2759 event,
2760 &position_map,
2761 text_bounds,
2762 &interactive_bounds,
2763 &stacking_order,
2764 cx,
2765 )
2766 });
2767 }
2768 }
2769 });
2770 cx.on_mouse_event({
2771 let position_map = layout.position_map.clone();
2772 let editor = self.editor.clone();
2773 let stacking_order = cx.stacking_order().clone();
2774
2775 move |event: &MouseMoveEvent, phase, cx| {
2776 // if editor.has_pending_selection() && event.pressed_button == Some(MouseButton::Left) {
2777
2778 if phase == DispatchPhase::Bubble {
2779 editor.update(cx, |editor, cx| {
2780 if event.pressed_button == Some(MouseButton::Left) {
2781 Self::mouse_dragged(
2782 editor,
2783 event,
2784 &position_map,
2785 text_bounds,
2786 gutter_bounds,
2787 &stacking_order,
2788 cx,
2789 )
2790 }
2791
2792 if interactive_bounds.visibly_contains(&event.position, cx) {
2793 Self::mouse_moved(
2794 editor,
2795 event,
2796 &position_map,
2797 text_bounds,
2798 gutter_bounds,
2799 &stacking_order,
2800 cx,
2801 )
2802 }
2803 });
2804 }
2805 }
2806 });
2807 }
2808}
2809
2810#[derive(Debug)]
2811pub(crate) struct LineWithInvisibles {
2812 pub line: ShapedLine,
2813 invisibles: Vec<Invisible>,
2814}
2815
2816impl LineWithInvisibles {
2817 fn from_chunks<'a>(
2818 chunks: impl Iterator<Item = HighlightedChunk<'a>>,
2819 text_style: &TextStyle,
2820 max_line_len: usize,
2821 max_line_count: usize,
2822 line_number_layouts: &[Option<ShapedLine>],
2823 editor_mode: EditorMode,
2824 cx: &WindowContext,
2825 ) -> Vec<Self> {
2826 let mut layouts = Vec::with_capacity(max_line_count);
2827 let mut line = String::new();
2828 let mut invisibles = Vec::new();
2829 let mut styles = Vec::new();
2830 let mut non_whitespace_added = false;
2831 let mut row = 0;
2832 let mut line_exceeded_max_len = false;
2833 let font_size = text_style.font_size.to_pixels(cx.rem_size());
2834
2835 for highlighted_chunk in chunks.chain([HighlightedChunk {
2836 chunk: "\n",
2837 style: None,
2838 is_tab: false,
2839 }]) {
2840 for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() {
2841 if ix > 0 {
2842 let shaped_line = cx
2843 .text_system()
2844 .shape_line(line.clone().into(), font_size, &styles)
2845 .unwrap();
2846 layouts.push(Self {
2847 line: shaped_line,
2848 invisibles: invisibles.drain(..).collect(),
2849 });
2850
2851 line.clear();
2852 styles.clear();
2853 row += 1;
2854 line_exceeded_max_len = false;
2855 non_whitespace_added = false;
2856 if row == max_line_count {
2857 return layouts;
2858 }
2859 }
2860
2861 if !line_chunk.is_empty() && !line_exceeded_max_len {
2862 let text_style = if let Some(style) = highlighted_chunk.style {
2863 Cow::Owned(text_style.clone().highlight(style))
2864 } else {
2865 Cow::Borrowed(text_style)
2866 };
2867
2868 if line.len() + line_chunk.len() > max_line_len {
2869 let mut chunk_len = max_line_len - line.len();
2870 while !line_chunk.is_char_boundary(chunk_len) {
2871 chunk_len -= 1;
2872 }
2873 line_chunk = &line_chunk[..chunk_len];
2874 line_exceeded_max_len = true;
2875 }
2876
2877 styles.push(TextRun {
2878 len: line_chunk.len(),
2879 font: text_style.font(),
2880 color: text_style.color,
2881 background_color: text_style.background_color,
2882 underline: text_style.underline,
2883 strikethrough: text_style.strikethrough,
2884 });
2885
2886 if editor_mode == EditorMode::Full {
2887 // Line wrap pads its contents with fake whitespaces,
2888 // avoid printing them
2889 let inside_wrapped_string = line_number_layouts
2890 .get(row)
2891 .and_then(|layout| layout.as_ref())
2892 .is_none();
2893 if highlighted_chunk.is_tab {
2894 if non_whitespace_added || !inside_wrapped_string {
2895 invisibles.push(Invisible::Tab {
2896 line_start_offset: line.len(),
2897 });
2898 }
2899 } else {
2900 invisibles.extend(
2901 line_chunk
2902 .chars()
2903 .enumerate()
2904 .filter(|(_, line_char)| {
2905 let is_whitespace = line_char.is_whitespace();
2906 non_whitespace_added |= !is_whitespace;
2907 is_whitespace
2908 && (non_whitespace_added || !inside_wrapped_string)
2909 })
2910 .map(|(whitespace_index, _)| Invisible::Whitespace {
2911 line_offset: line.len() + whitespace_index,
2912 }),
2913 )
2914 }
2915 }
2916
2917 line.push_str(line_chunk);
2918 }
2919 }
2920 }
2921
2922 layouts
2923 }
2924
2925 fn draw(
2926 &self,
2927 layout: &LayoutState,
2928 row: u32,
2929 content_origin: gpui::Point<Pixels>,
2930 whitespace_setting: ShowWhitespaceSetting,
2931 selection_ranges: &[Range<DisplayPoint>],
2932 cx: &mut ElementContext,
2933 ) {
2934 let line_height = layout.position_map.line_height;
2935 let line_y = line_height * row as f32 - layout.position_map.scroll_position.y;
2936
2937 self.line
2938 .paint(
2939 content_origin + gpui::point(-layout.position_map.scroll_position.x, line_y),
2940 line_height,
2941 cx,
2942 )
2943 .log_err();
2944
2945 self.draw_invisibles(
2946 &selection_ranges,
2947 layout,
2948 content_origin,
2949 line_y,
2950 row,
2951 line_height,
2952 whitespace_setting,
2953 cx,
2954 );
2955 }
2956
2957 fn draw_invisibles(
2958 &self,
2959 selection_ranges: &[Range<DisplayPoint>],
2960 layout: &LayoutState,
2961 content_origin: gpui::Point<Pixels>,
2962 line_y: Pixels,
2963 row: u32,
2964 line_height: Pixels,
2965 whitespace_setting: ShowWhitespaceSetting,
2966 cx: &mut ElementContext,
2967 ) {
2968 let allowed_invisibles_regions = match whitespace_setting {
2969 ShowWhitespaceSetting::None => return,
2970 ShowWhitespaceSetting::Selection => Some(selection_ranges),
2971 ShowWhitespaceSetting::All => None,
2972 };
2973
2974 for invisible in &self.invisibles {
2975 let (&token_offset, invisible_symbol) = match invisible {
2976 Invisible::Tab { line_start_offset } => (line_start_offset, &layout.tab_invisible),
2977 Invisible::Whitespace { line_offset } => (line_offset, &layout.space_invisible),
2978 };
2979
2980 let x_offset = self.line.x_for_index(token_offset);
2981 let invisible_offset =
2982 (layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
2983 let origin = content_origin
2984 + gpui::point(
2985 x_offset + invisible_offset - layout.position_map.scroll_position.x,
2986 line_y,
2987 );
2988
2989 if let Some(allowed_regions) = allowed_invisibles_regions {
2990 let invisible_point = DisplayPoint::new(row, token_offset as u32);
2991 if !allowed_regions
2992 .iter()
2993 .any(|region| region.start <= invisible_point && invisible_point < region.end)
2994 {
2995 continue;
2996 }
2997 }
2998 invisible_symbol.paint(origin, line_height, cx).log_err();
2999 }
3000 }
3001}
3002
3003#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3004enum Invisible {
3005 Tab { line_start_offset: usize },
3006 Whitespace { line_offset: usize },
3007}
3008
3009impl Element for EditorElement {
3010 type State = ();
3011
3012 fn request_layout(
3013 &mut self,
3014 _element_state: Option<Self::State>,
3015 cx: &mut gpui::ElementContext,
3016 ) -> (gpui::LayoutId, Self::State) {
3017 cx.with_view_id(self.editor.entity_id(), |cx| {
3018 self.editor.update(cx, |editor, cx| {
3019 editor.set_style(self.style.clone(), cx);
3020
3021 let layout_id = match editor.mode {
3022 EditorMode::SingleLine => {
3023 let rem_size = cx.rem_size();
3024 let mut style = Style::default();
3025 style.size.width = relative(1.).into();
3026 style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
3027 cx.with_element_context(|cx| cx.request_layout(&style, None))
3028 }
3029 EditorMode::AutoHeight { max_lines } => {
3030 let editor_handle = cx.view().clone();
3031 let max_line_number_width =
3032 self.max_line_number_width(&editor.snapshot(cx), cx);
3033 cx.with_element_context(|cx| {
3034 cx.request_measured_layout(
3035 Style::default(),
3036 move |known_dimensions, _, cx| {
3037 editor_handle
3038 .update(cx, |editor, cx| {
3039 compute_auto_height_layout(
3040 editor,
3041 max_lines,
3042 max_line_number_width,
3043 known_dimensions,
3044 cx,
3045 )
3046 })
3047 .unwrap_or_default()
3048 },
3049 )
3050 })
3051 }
3052 EditorMode::Full => {
3053 let mut style = Style::default();
3054 style.size.width = relative(1.).into();
3055 style.size.height = relative(1.).into();
3056 cx.with_element_context(|cx| cx.request_layout(&style, None))
3057 }
3058 };
3059
3060 (layout_id, ())
3061 })
3062 })
3063 }
3064
3065 fn paint(
3066 &mut self,
3067 bounds: Bounds<gpui::Pixels>,
3068 _element_state: &mut Self::State,
3069 cx: &mut gpui::ElementContext,
3070 ) {
3071 let editor = self.editor.clone();
3072
3073 cx.paint_view(self.editor.entity_id(), |cx| {
3074 cx.with_text_style(
3075 Some(gpui::TextStyleRefinement {
3076 font_size: Some(self.style.text.font_size),
3077 line_height: Some(self.style.text.line_height),
3078 ..Default::default()
3079 }),
3080 |cx| {
3081 let mut layout = self.compute_layout(bounds, cx);
3082 let gutter_bounds = Bounds {
3083 origin: bounds.origin,
3084 size: layout.gutter_size,
3085 };
3086 let text_bounds = Bounds {
3087 origin: gutter_bounds.upper_right(),
3088 size: layout.text_size,
3089 };
3090
3091 let focus_handle = editor.focus_handle(cx);
3092 let key_context = self.editor.read(cx).key_context(cx);
3093 cx.with_key_dispatch(Some(key_context), Some(focus_handle.clone()), |_, cx| {
3094 self.register_actions(cx);
3095
3096 cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
3097 self.register_key_listeners(cx, text_bounds, &layout);
3098 cx.handle_input(
3099 &focus_handle,
3100 ElementInputHandler::new(bounds, self.editor.clone()),
3101 );
3102
3103 self.paint_background(gutter_bounds, text_bounds, &layout, cx);
3104 if layout.gutter_size.width > Pixels::ZERO {
3105 self.paint_gutter(gutter_bounds, &mut layout, cx);
3106 }
3107 self.paint_text(text_bounds, &mut layout, cx);
3108
3109 cx.with_z_index(0, |cx| {
3110 self.paint_mouse_listeners(
3111 bounds,
3112 gutter_bounds,
3113 text_bounds,
3114 &layout,
3115 cx,
3116 );
3117 });
3118 if !layout.blocks.is_empty() {
3119 cx.with_z_index(0, |cx| {
3120 cx.with_element_id(Some("editor_blocks"), |cx| {
3121 self.paint_blocks(bounds, &mut layout, cx);
3122 });
3123 })
3124 }
3125
3126 cx.with_z_index(1, |cx| {
3127 self.paint_overlays(text_bounds, &mut layout, cx);
3128 });
3129
3130 cx.with_z_index(2, |cx| self.paint_scrollbar(bounds, &mut layout, cx));
3131 });
3132 })
3133 },
3134 )
3135 })
3136 }
3137}
3138
3139impl IntoElement for EditorElement {
3140 type Element = Self;
3141
3142 fn element_id(&self) -> Option<gpui::ElementId> {
3143 self.editor.element_id()
3144 }
3145
3146 fn into_element(self) -> Self::Element {
3147 self
3148 }
3149}
3150
3151type BufferRow = u32;
3152
3153pub struct LayoutState {
3154 position_map: Arc<PositionMap>,
3155 gutter_size: Size<Pixels>,
3156 gutter_padding: Pixels,
3157 gutter_margin: Pixels,
3158 text_size: gpui::Size<Pixels>,
3159 mode: EditorMode,
3160 wrap_guides: SmallVec<[(Pixels, bool); 2]>,
3161 visible_anchor_range: Range<Anchor>,
3162 visible_display_row_range: Range<u32>,
3163 active_rows: BTreeMap<u32, bool>,
3164 highlighted_rows: Option<Range<u32>>,
3165 line_numbers: Vec<Option<ShapedLine>>,
3166 display_hunks: Vec<DisplayDiffHunk>,
3167 blocks: Vec<BlockLayout>,
3168 highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
3169 redacted_ranges: Vec<Range<DisplayPoint>>,
3170 selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
3171 scrollbar_row_range: Range<f32>,
3172 show_scrollbars: bool,
3173 is_singleton: bool,
3174 max_row: u32,
3175 context_menu: Option<(DisplayPoint, AnyElement)>,
3176 code_actions_indicator: Option<CodeActionsIndicator>,
3177 hover_popovers: Option<(DisplayPoint, Vec<AnyElement>)>,
3178 fold_indicators: Vec<Option<IconButton>>,
3179 tab_invisible: ShapedLine,
3180 space_invisible: ShapedLine,
3181}
3182
3183impl LayoutState {
3184 fn line_end_overshoot(&self) -> Pixels {
3185 0.15 * self.position_map.line_height
3186 }
3187}
3188
3189struct CodeActionsIndicator {
3190 row: u32,
3191 button: IconButton,
3192}
3193
3194struct PositionMap {
3195 size: Size<Pixels>,
3196 line_height: Pixels,
3197 scroll_position: gpui::Point<Pixels>,
3198 scroll_max: gpui::Point<f32>,
3199 em_width: Pixels,
3200 em_advance: Pixels,
3201 line_layouts: Vec<LineWithInvisibles>,
3202 snapshot: EditorSnapshot,
3203}
3204
3205#[derive(Debug, Copy, Clone)]
3206pub struct PointForPosition {
3207 pub previous_valid: DisplayPoint,
3208 pub next_valid: DisplayPoint,
3209 pub exact_unclipped: DisplayPoint,
3210 pub column_overshoot_after_line_end: u32,
3211}
3212
3213impl PointForPosition {
3214 pub fn as_valid(&self) -> Option<DisplayPoint> {
3215 if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped {
3216 Some(self.previous_valid)
3217 } else {
3218 None
3219 }
3220 }
3221}
3222
3223impl PositionMap {
3224 fn point_for_position(
3225 &self,
3226 text_bounds: Bounds<Pixels>,
3227 position: gpui::Point<Pixels>,
3228 ) -> PointForPosition {
3229 let scroll_position = self.snapshot.scroll_position();
3230 let position = position - text_bounds.origin;
3231 let y = position.y.max(px(0.)).min(self.size.height);
3232 let x = position.x + (scroll_position.x * self.em_width);
3233 let row = (f32::from(y / self.line_height) + scroll_position.y) as u32;
3234
3235 let (column, x_overshoot_after_line_end) = if let Some(line) = self
3236 .line_layouts
3237 .get(row as usize - scroll_position.y as usize)
3238 .map(|&LineWithInvisibles { ref line, .. }| line)
3239 {
3240 if let Some(ix) = line.index_for_x(x) {
3241 (ix as u32, px(0.))
3242 } else {
3243 (line.len as u32, px(0.).max(x - line.width))
3244 }
3245 } else {
3246 (0, x)
3247 };
3248
3249 let mut exact_unclipped = DisplayPoint::new(row, column);
3250 let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
3251 let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
3252
3253 let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32;
3254 *exact_unclipped.column_mut() += column_overshoot_after_line_end;
3255 PointForPosition {
3256 previous_valid,
3257 next_valid,
3258 exact_unclipped,
3259 column_overshoot_after_line_end,
3260 }
3261 }
3262}
3263
3264struct BlockLayout {
3265 row: u32,
3266 element: AnyElement,
3267 available_space: Size<AvailableSpace>,
3268 style: BlockStyle,
3269}
3270
3271fn layout_line(
3272 row: u32,
3273 snapshot: &EditorSnapshot,
3274 style: &EditorStyle,
3275 cx: &WindowContext,
3276) -> Result<ShapedLine> {
3277 let mut line = snapshot.line(row);
3278
3279 if line.len() > MAX_LINE_LEN {
3280 let mut len = MAX_LINE_LEN;
3281 while !line.is_char_boundary(len) {
3282 len -= 1;
3283 }
3284
3285 line.truncate(len);
3286 }
3287
3288 cx.text_system().shape_line(
3289 line.into(),
3290 style.text.font_size.to_pixels(cx.rem_size()),
3291 &[TextRun {
3292 len: snapshot.line_len(row) as usize,
3293 font: style.text.font(),
3294 color: Hsla::default(),
3295 background_color: None,
3296 underline: None,
3297 strikethrough: None,
3298 }],
3299 )
3300}
3301
3302#[derive(Debug)]
3303pub struct Cursor {
3304 origin: gpui::Point<Pixels>,
3305 block_width: Pixels,
3306 line_height: Pixels,
3307 color: Hsla,
3308 shape: CursorShape,
3309 block_text: Option<ShapedLine>,
3310 cursor_name: Option<CursorName>,
3311}
3312
3313#[derive(Debug)]
3314pub struct CursorName {
3315 string: SharedString,
3316 color: Hsla,
3317 is_top_row: bool,
3318 z_index: u16,
3319}
3320
3321impl Cursor {
3322 pub fn new(
3323 origin: gpui::Point<Pixels>,
3324 block_width: Pixels,
3325 line_height: Pixels,
3326 color: Hsla,
3327 shape: CursorShape,
3328 block_text: Option<ShapedLine>,
3329 cursor_name: Option<CursorName>,
3330 ) -> Cursor {
3331 Cursor {
3332 origin,
3333 block_width,
3334 line_height,
3335 color,
3336 shape,
3337 block_text,
3338 cursor_name,
3339 }
3340 }
3341
3342 pub fn bounding_rect(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
3343 Bounds {
3344 origin: self.origin + origin,
3345 size: size(self.block_width, self.line_height),
3346 }
3347 }
3348
3349 pub fn paint(&self, origin: gpui::Point<Pixels>, cx: &mut ElementContext) {
3350 let bounds = match self.shape {
3351 CursorShape::Bar => Bounds {
3352 origin: self.origin + origin,
3353 size: size(px(2.0), self.line_height),
3354 },
3355 CursorShape::Block | CursorShape::Hollow => Bounds {
3356 origin: self.origin + origin,
3357 size: size(self.block_width, self.line_height),
3358 },
3359 CursorShape::Underscore => Bounds {
3360 origin: self.origin
3361 + origin
3362 + gpui::Point::new(Pixels::ZERO, self.line_height - px(2.0)),
3363 size: size(self.block_width, px(2.0)),
3364 },
3365 };
3366
3367 //Draw background or border quad
3368 let cursor = if matches!(self.shape, CursorShape::Hollow) {
3369 outline(bounds, self.color)
3370 } else {
3371 fill(bounds, self.color)
3372 };
3373
3374 if let Some(name) = &self.cursor_name {
3375 let text_size = self.line_height / 1.5;
3376
3377 let name_origin = if name.is_top_row {
3378 point(bounds.right() - px(1.), bounds.top())
3379 } else {
3380 point(bounds.left(), bounds.top() - text_size / 2. - px(1.))
3381 };
3382 cx.with_z_index(name.z_index, |cx| {
3383 div()
3384 .bg(self.color)
3385 .text_size(text_size)
3386 .px_0p5()
3387 .line_height(text_size + px(2.))
3388 .text_color(name.color)
3389 .child(name.string.clone())
3390 .into_any_element()
3391 .draw(
3392 name_origin,
3393 size(AvailableSpace::MinContent, AvailableSpace::MinContent),
3394 cx,
3395 )
3396 })
3397 }
3398
3399 cx.paint_quad(cursor);
3400
3401 if let Some(block_text) = &self.block_text {
3402 block_text
3403 .paint(self.origin + origin, self.line_height, cx)
3404 .log_err();
3405 }
3406 }
3407
3408 pub fn shape(&self) -> CursorShape {
3409 self.shape
3410 }
3411}
3412
3413#[derive(Debug)]
3414pub struct HighlightedRange {
3415 pub start_y: Pixels,
3416 pub line_height: Pixels,
3417 pub lines: Vec<HighlightedRangeLine>,
3418 pub color: Hsla,
3419 pub corner_radius: Pixels,
3420}
3421
3422#[derive(Debug)]
3423pub struct HighlightedRangeLine {
3424 pub start_x: Pixels,
3425 pub end_x: Pixels,
3426}
3427
3428impl HighlightedRange {
3429 pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut ElementContext) {
3430 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
3431 self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
3432 self.paint_lines(
3433 self.start_y + self.line_height,
3434 &self.lines[1..],
3435 bounds,
3436 cx,
3437 );
3438 } else {
3439 self.paint_lines(self.start_y, &self.lines, bounds, cx);
3440 }
3441 }
3442
3443 fn paint_lines(
3444 &self,
3445 start_y: Pixels,
3446 lines: &[HighlightedRangeLine],
3447 _bounds: Bounds<Pixels>,
3448 cx: &mut ElementContext,
3449 ) {
3450 if lines.is_empty() {
3451 return;
3452 }
3453
3454 let first_line = lines.first().unwrap();
3455 let last_line = lines.last().unwrap();
3456
3457 let first_top_left = point(first_line.start_x, start_y);
3458 let first_top_right = point(first_line.end_x, start_y);
3459
3460 let curve_height = point(Pixels::ZERO, self.corner_radius);
3461 let curve_width = |start_x: Pixels, end_x: Pixels| {
3462 let max = (end_x - start_x) / 2.;
3463 let width = if max < self.corner_radius {
3464 max
3465 } else {
3466 self.corner_radius
3467 };
3468
3469 point(width, Pixels::ZERO)
3470 };
3471
3472 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
3473 let mut path = gpui::Path::new(first_top_right - top_curve_width);
3474 path.curve_to(first_top_right + curve_height, first_top_right);
3475
3476 let mut iter = lines.iter().enumerate().peekable();
3477 while let Some((ix, line)) = iter.next() {
3478 let bottom_right = point(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
3479
3480 if let Some((_, next_line)) = iter.peek() {
3481 let next_top_right = point(next_line.end_x, bottom_right.y);
3482
3483 match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
3484 Ordering::Equal => {
3485 path.line_to(bottom_right);
3486 }
3487 Ordering::Less => {
3488 let curve_width = curve_width(next_top_right.x, bottom_right.x);
3489 path.line_to(bottom_right - curve_height);
3490 if self.corner_radius > Pixels::ZERO {
3491 path.curve_to(bottom_right - curve_width, bottom_right);
3492 }
3493 path.line_to(next_top_right + curve_width);
3494 if self.corner_radius > Pixels::ZERO {
3495 path.curve_to(next_top_right + curve_height, next_top_right);
3496 }
3497 }
3498 Ordering::Greater => {
3499 let curve_width = curve_width(bottom_right.x, next_top_right.x);
3500 path.line_to(bottom_right - curve_height);
3501 if self.corner_radius > Pixels::ZERO {
3502 path.curve_to(bottom_right + curve_width, bottom_right);
3503 }
3504 path.line_to(next_top_right - curve_width);
3505 if self.corner_radius > Pixels::ZERO {
3506 path.curve_to(next_top_right + curve_height, next_top_right);
3507 }
3508 }
3509 }
3510 } else {
3511 let curve_width = curve_width(line.start_x, line.end_x);
3512 path.line_to(bottom_right - curve_height);
3513 if self.corner_radius > Pixels::ZERO {
3514 path.curve_to(bottom_right - curve_width, bottom_right);
3515 }
3516
3517 let bottom_left = point(line.start_x, bottom_right.y);
3518 path.line_to(bottom_left + curve_width);
3519 if self.corner_radius > Pixels::ZERO {
3520 path.curve_to(bottom_left - curve_height, bottom_left);
3521 }
3522 }
3523 }
3524
3525 if first_line.start_x > last_line.start_x {
3526 let curve_width = curve_width(last_line.start_x, first_line.start_x);
3527 let second_top_left = point(last_line.start_x, start_y + self.line_height);
3528 path.line_to(second_top_left + curve_height);
3529 if self.corner_radius > Pixels::ZERO {
3530 path.curve_to(second_top_left + curve_width, second_top_left);
3531 }
3532 let first_bottom_left = point(first_line.start_x, second_top_left.y);
3533 path.line_to(first_bottom_left - curve_width);
3534 if self.corner_radius > Pixels::ZERO {
3535 path.curve_to(first_bottom_left - curve_height, first_bottom_left);
3536 }
3537 }
3538
3539 path.line_to(first_top_left + curve_height);
3540 if self.corner_radius > Pixels::ZERO {
3541 path.curve_to(first_top_left + top_curve_width, first_top_left);
3542 }
3543 path.line_to(first_top_right - top_curve_width);
3544
3545 cx.paint_path(path, self.color);
3546 }
3547}
3548
3549pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
3550 (delta.pow(1.5) / 100.0).into()
3551}
3552
3553fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
3554 (delta.pow(1.2) / 300.0).into()
3555}
3556
3557#[cfg(test)]
3558mod tests {
3559 use super::*;
3560 use crate::{
3561 display_map::{BlockDisposition, BlockProperties},
3562 editor_tests::{init_test, update_test_language_settings},
3563 Editor, MultiBuffer,
3564 };
3565 use gpui::TestAppContext;
3566 use language::language_settings;
3567 use log::info;
3568 use std::{num::NonZeroU32, sync::Arc};
3569 use util::test::sample_text;
3570
3571 #[gpui::test]
3572 fn test_shape_line_numbers(cx: &mut TestAppContext) {
3573 init_test(cx, |_| {});
3574 let window = cx.add_window(|cx| {
3575 let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
3576 Editor::new(EditorMode::Full, buffer, None, cx)
3577 });
3578
3579 let editor = window.root(cx).unwrap();
3580 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
3581 let element = EditorElement::new(&editor, style);
3582
3583 let layouts = window
3584 .update(cx, |editor, cx| {
3585 let snapshot = editor.snapshot(cx);
3586 element
3587 .shape_line_numbers(
3588 0..6,
3589 &Default::default(),
3590 DisplayPoint::new(0, 0),
3591 false,
3592 &snapshot,
3593 cx,
3594 )
3595 .0
3596 })
3597 .unwrap();
3598 assert_eq!(layouts.len(), 6);
3599
3600 let relative_rows = window
3601 .update(cx, |editor, cx| {
3602 let snapshot = editor.snapshot(cx);
3603 element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3))
3604 })
3605 .unwrap();
3606 assert_eq!(relative_rows[&0], 3);
3607 assert_eq!(relative_rows[&1], 2);
3608 assert_eq!(relative_rows[&2], 1);
3609 // current line has no relative number
3610 assert_eq!(relative_rows[&4], 1);
3611 assert_eq!(relative_rows[&5], 2);
3612
3613 // works if cursor is before screen
3614 let relative_rows = window
3615 .update(cx, |editor, cx| {
3616 let snapshot = editor.snapshot(cx);
3617
3618 element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1))
3619 })
3620 .unwrap();
3621 assert_eq!(relative_rows.len(), 3);
3622 assert_eq!(relative_rows[&3], 2);
3623 assert_eq!(relative_rows[&4], 3);
3624 assert_eq!(relative_rows[&5], 4);
3625
3626 // works if cursor is after screen
3627 let relative_rows = window
3628 .update(cx, |editor, cx| {
3629 let snapshot = editor.snapshot(cx);
3630
3631 element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6))
3632 })
3633 .unwrap();
3634 assert_eq!(relative_rows.len(), 3);
3635 assert_eq!(relative_rows[&0], 5);
3636 assert_eq!(relative_rows[&1], 4);
3637 assert_eq!(relative_rows[&2], 3);
3638 }
3639
3640 #[gpui::test]
3641 async fn test_vim_visual_selections(cx: &mut TestAppContext) {
3642 init_test(cx, |_| {});
3643
3644 let window = cx.add_window(|cx| {
3645 let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
3646 Editor::new(EditorMode::Full, buffer, None, cx)
3647 });
3648 let editor = window.root(cx).unwrap();
3649 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
3650 let mut element = EditorElement::new(&editor, style);
3651
3652 window
3653 .update(cx, |editor, cx| {
3654 editor.cursor_shape = CursorShape::Block;
3655 editor.change_selections(None, cx, |s| {
3656 s.select_ranges([
3657 Point::new(0, 0)..Point::new(1, 0),
3658 Point::new(3, 2)..Point::new(3, 3),
3659 Point::new(5, 6)..Point::new(6, 0),
3660 ]);
3661 });
3662 })
3663 .unwrap();
3664 let state = cx
3665 .update_window(window.into(), |view, cx| {
3666 cx.with_element_context(|cx| {
3667 cx.with_view_id(view.entity_id(), |cx| {
3668 element.compute_layout(
3669 Bounds {
3670 origin: point(px(500.), px(500.)),
3671 size: size(px(500.), px(500.)),
3672 },
3673 cx,
3674 )
3675 })
3676 })
3677 })
3678 .unwrap();
3679
3680 assert_eq!(state.selections.len(), 1);
3681 let local_selections = &state.selections[0].1;
3682 assert_eq!(local_selections.len(), 3);
3683 // moves cursor back one line
3684 assert_eq!(local_selections[0].head, DisplayPoint::new(0, 6));
3685 assert_eq!(
3686 local_selections[0].range,
3687 DisplayPoint::new(0, 0)..DisplayPoint::new(1, 0)
3688 );
3689
3690 // moves cursor back one column
3691 assert_eq!(
3692 local_selections[1].range,
3693 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3)
3694 );
3695 assert_eq!(local_selections[1].head, DisplayPoint::new(3, 2));
3696
3697 // leaves cursor on the max point
3698 assert_eq!(
3699 local_selections[2].range,
3700 DisplayPoint::new(5, 6)..DisplayPoint::new(6, 0)
3701 );
3702 assert_eq!(local_selections[2].head, DisplayPoint::new(6, 0));
3703
3704 // active lines does not include 1 (even though the range of the selection does)
3705 assert_eq!(
3706 state.active_rows.keys().cloned().collect::<Vec<u32>>(),
3707 vec![0, 3, 5, 6]
3708 );
3709
3710 // multi-buffer support
3711 // in DisplayPoint coordinates, this is what we're dealing with:
3712 // 0: [[file
3713 // 1: header]]
3714 // 2: aaaaaa
3715 // 3: bbbbbb
3716 // 4: cccccc
3717 // 5:
3718 // 6: ...
3719 // 7: ffffff
3720 // 8: gggggg
3721 // 9: hhhhhh
3722 // 10:
3723 // 11: [[file
3724 // 12: header]]
3725 // 13: bbbbbb
3726 // 14: cccccc
3727 // 15: dddddd
3728 let window = cx.add_window(|cx| {
3729 let buffer = MultiBuffer::build_multi(
3730 [
3731 (
3732 &(sample_text(8, 6, 'a') + "\n"),
3733 vec![
3734 Point::new(0, 0)..Point::new(3, 0),
3735 Point::new(4, 0)..Point::new(7, 0),
3736 ],
3737 ),
3738 (
3739 &(sample_text(8, 6, 'a') + "\n"),
3740 vec![Point::new(1, 0)..Point::new(3, 0)],
3741 ),
3742 ],
3743 cx,
3744 );
3745 Editor::new(EditorMode::Full, buffer, None, cx)
3746 });
3747 let editor = window.root(cx).unwrap();
3748 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
3749 let mut element = EditorElement::new(&editor, style);
3750 let _state = window.update(cx, |editor, cx| {
3751 editor.cursor_shape = CursorShape::Block;
3752 editor.change_selections(None, cx, |s| {
3753 s.select_display_ranges([
3754 DisplayPoint::new(4, 0)..DisplayPoint::new(7, 0),
3755 DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
3756 ]);
3757 });
3758 });
3759
3760 let state = cx
3761 .update_window(window.into(), |view, cx| {
3762 cx.with_element_context(|cx| {
3763 cx.with_view_id(view.entity_id(), |cx| {
3764 element.compute_layout(
3765 Bounds {
3766 origin: point(px(500.), px(500.)),
3767 size: size(px(500.), px(500.)),
3768 },
3769 cx,
3770 )
3771 })
3772 })
3773 })
3774 .unwrap();
3775 assert_eq!(state.selections.len(), 1);
3776 let local_selections = &state.selections[0].1;
3777 assert_eq!(local_selections.len(), 2);
3778
3779 // moves cursor on excerpt boundary back a line
3780 // and doesn't allow selection to bleed through
3781 assert_eq!(
3782 local_selections[0].range,
3783 DisplayPoint::new(4, 0)..DisplayPoint::new(6, 0)
3784 );
3785 assert_eq!(local_selections[0].head, DisplayPoint::new(5, 0));
3786 // moves cursor on buffer boundary back two lines
3787 // and doesn't allow selection to bleed through
3788 assert_eq!(
3789 local_selections[1].range,
3790 DisplayPoint::new(10, 0)..DisplayPoint::new(11, 0)
3791 );
3792 assert_eq!(local_selections[1].head, DisplayPoint::new(10, 0));
3793 }
3794
3795 #[gpui::test]
3796 fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
3797 init_test(cx, |_| {});
3798
3799 let window = cx.add_window(|cx| {
3800 let buffer = MultiBuffer::build_simple("", cx);
3801 Editor::new(EditorMode::Full, buffer, None, cx)
3802 });
3803 let editor = window.root(cx).unwrap();
3804 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
3805 window
3806 .update(cx, |editor, cx| {
3807 editor.set_placeholder_text("hello", cx);
3808 editor.insert_blocks(
3809 [BlockProperties {
3810 style: BlockStyle::Fixed,
3811 disposition: BlockDisposition::Above,
3812 height: 3,
3813 position: Anchor::min(),
3814 render: Arc::new(|_| div().into_any()),
3815 }],
3816 None,
3817 cx,
3818 );
3819
3820 // Blur the editor so that it displays placeholder text.
3821 cx.blur();
3822 })
3823 .unwrap();
3824
3825 let mut element = EditorElement::new(&editor, style);
3826 let state = cx
3827 .update_window(window.into(), |view, cx| {
3828 cx.with_element_context(|cx| {
3829 cx.with_view_id(view.entity_id(), |cx| {
3830 element.compute_layout(
3831 Bounds {
3832 origin: point(px(500.), px(500.)),
3833 size: size(px(500.), px(500.)),
3834 },
3835 cx,
3836 )
3837 })
3838 })
3839 })
3840 .unwrap();
3841 let size = state.position_map.size;
3842
3843 assert_eq!(state.position_map.line_layouts.len(), 4);
3844 assert_eq!(
3845 state
3846 .line_numbers
3847 .iter()
3848 .map(Option::is_some)
3849 .collect::<Vec<_>>(),
3850 &[false, false, false, true]
3851 );
3852
3853 // Don't panic.
3854 let bounds = Bounds::<Pixels>::new(Default::default(), size);
3855 cx.update_window(window.into(), |_, cx| {
3856 cx.with_element_context(|cx| element.paint(bounds, &mut (), cx))
3857 })
3858 .unwrap()
3859 }
3860
3861 #[gpui::test]
3862 fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
3863 const TAB_SIZE: u32 = 4;
3864
3865 let input_text = "\t \t|\t| a b";
3866 let expected_invisibles = vec![
3867 Invisible::Tab {
3868 line_start_offset: 0,
3869 },
3870 Invisible::Whitespace {
3871 line_offset: TAB_SIZE as usize,
3872 },
3873 Invisible::Tab {
3874 line_start_offset: TAB_SIZE as usize + 1,
3875 },
3876 Invisible::Tab {
3877 line_start_offset: TAB_SIZE as usize * 2 + 1,
3878 },
3879 Invisible::Whitespace {
3880 line_offset: TAB_SIZE as usize * 3 + 1,
3881 },
3882 Invisible::Whitespace {
3883 line_offset: TAB_SIZE as usize * 3 + 3,
3884 },
3885 ];
3886 assert_eq!(
3887 expected_invisibles.len(),
3888 input_text
3889 .chars()
3890 .filter(|initial_char| initial_char.is_whitespace())
3891 .count(),
3892 "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
3893 );
3894
3895 init_test(cx, |s| {
3896 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
3897 s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
3898 });
3899
3900 let actual_invisibles =
3901 collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, px(500.0));
3902
3903 assert_eq!(expected_invisibles, actual_invisibles);
3904 }
3905
3906 #[gpui::test]
3907 fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
3908 init_test(cx, |s| {
3909 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
3910 s.defaults.tab_size = NonZeroU32::new(4);
3911 });
3912
3913 for editor_mode_without_invisibles in [
3914 EditorMode::SingleLine,
3915 EditorMode::AutoHeight { max_lines: 100 },
3916 ] {
3917 let invisibles = collect_invisibles_from_new_editor(
3918 cx,
3919 editor_mode_without_invisibles,
3920 "\t\t\t| | a b",
3921 px(500.0),
3922 );
3923 assert!(invisibles.is_empty(),
3924 "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}");
3925 }
3926 }
3927
3928 #[gpui::test]
3929 fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
3930 let tab_size = 4;
3931 let input_text = "a\tbcd ".repeat(9);
3932 let repeated_invisibles = [
3933 Invisible::Tab {
3934 line_start_offset: 1,
3935 },
3936 Invisible::Whitespace {
3937 line_offset: tab_size as usize + 3,
3938 },
3939 Invisible::Whitespace {
3940 line_offset: tab_size as usize + 4,
3941 },
3942 Invisible::Whitespace {
3943 line_offset: tab_size as usize + 5,
3944 },
3945 ];
3946 let expected_invisibles = std::iter::once(repeated_invisibles)
3947 .cycle()
3948 .take(9)
3949 .flatten()
3950 .collect::<Vec<_>>();
3951 assert_eq!(
3952 expected_invisibles.len(),
3953 input_text
3954 .chars()
3955 .filter(|initial_char| initial_char.is_whitespace())
3956 .count(),
3957 "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
3958 );
3959 info!("Expected invisibles: {expected_invisibles:?}");
3960
3961 init_test(cx, |_| {});
3962
3963 // Put the same string with repeating whitespace pattern into editors of various size,
3964 // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
3965 let resize_step = 10.0;
3966 let mut editor_width = 200.0;
3967 while editor_width <= 1000.0 {
3968 update_test_language_settings(cx, |s| {
3969 s.defaults.tab_size = NonZeroU32::new(tab_size);
3970 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
3971 s.defaults.preferred_line_length = Some(editor_width as u32);
3972 s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
3973 });
3974
3975 let actual_invisibles = collect_invisibles_from_new_editor(
3976 cx,
3977 EditorMode::Full,
3978 &input_text,
3979 px(editor_width),
3980 );
3981
3982 // Whatever the editor size is, ensure it has the same invisible kinds in the same order
3983 // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
3984 let mut i = 0;
3985 for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
3986 i = actual_index;
3987 match expected_invisibles.get(i) {
3988 Some(expected_invisible) => match (expected_invisible, actual_invisible) {
3989 (Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
3990 | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
3991 _ => {
3992 panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}")
3993 }
3994 },
3995 None => panic!("Unexpected extra invisible {actual_invisible:?} at index {i}"),
3996 }
3997 }
3998 let missing_expected_invisibles = &expected_invisibles[i + 1..];
3999 assert!(
4000 missing_expected_invisibles.is_empty(),
4001 "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
4002 );
4003
4004 editor_width += resize_step;
4005 }
4006 }
4007
4008 fn collect_invisibles_from_new_editor(
4009 cx: &mut TestAppContext,
4010 editor_mode: EditorMode,
4011 input_text: &str,
4012 editor_width: Pixels,
4013 ) -> Vec<Invisible> {
4014 info!(
4015 "Creating editor with mode {editor_mode:?}, width {}px and text '{input_text}'",
4016 editor_width.0
4017 );
4018 let window = cx.add_window(|cx| {
4019 let buffer = MultiBuffer::build_simple(&input_text, cx);
4020 Editor::new(editor_mode, buffer, None, cx)
4021 });
4022 let editor = window.root(cx).unwrap();
4023 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
4024 let mut element = EditorElement::new(&editor, style);
4025 window
4026 .update(cx, |editor, cx| {
4027 editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
4028 editor.set_wrap_width(Some(editor_width), cx);
4029 })
4030 .unwrap();
4031 let layout_state = cx
4032 .update_window(window.into(), |_, cx| {
4033 cx.with_element_context(|cx| {
4034 element.compute_layout(
4035 Bounds {
4036 origin: point(px(500.), px(500.)),
4037 size: size(px(500.), px(500.)),
4038 },
4039 cx,
4040 )
4041 })
4042 })
4043 .unwrap();
4044
4045 layout_state
4046 .position_map
4047 .line_layouts
4048 .iter()
4049 .map(|line_with_invisibles| &line_with_invisibles.invisibles)
4050 .flatten()
4051 .cloned()
4052 .collect()
4053 }
4054}
4055
4056pub fn register_action<T: Action>(
4057 view: &View<Editor>,
4058 cx: &mut WindowContext,
4059 listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
4060) {
4061 let view = view.clone();
4062 cx.on_action(TypeId::of::<T>(), move |action, phase, cx| {
4063 let action = action.downcast_ref().unwrap();
4064 if phase == DispatchPhase::Bubble {
4065 view.update(cx, |editor, cx| {
4066 listener(editor, action, cx);
4067 })
4068 }
4069 })
4070}
4071
4072fn compute_auto_height_layout(
4073 editor: &mut Editor,
4074 max_lines: usize,
4075 max_line_number_width: Pixels,
4076 known_dimensions: Size<Option<Pixels>>,
4077 cx: &mut ViewContext<Editor>,
4078) -> Option<Size<Pixels>> {
4079 let width = known_dimensions.width?;
4080 if let Some(height) = known_dimensions.height {
4081 return Some(size(width, height));
4082 }
4083
4084 let style = editor.style.as_ref().unwrap();
4085 let font_id = cx.text_system().resolve_font(&style.text.font());
4086 let font_size = style.text.font_size.to_pixels(cx.rem_size());
4087 let line_height = style.text.line_height_in_pixels(cx.rem_size());
4088 let em_width = cx
4089 .text_system()
4090 .typographic_bounds(font_id, font_size, 'm')
4091 .unwrap()
4092 .size
4093 .width;
4094
4095 let mut snapshot = editor.snapshot(cx);
4096 let gutter_dimensions =
4097 snapshot.gutter_dimensions(font_id, font_size, em_width, max_line_number_width, cx);
4098
4099 editor.gutter_width = gutter_dimensions.width;
4100 let text_width = width - gutter_dimensions.width;
4101 let overscroll = size(em_width, px(0.));
4102
4103 let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width;
4104 if editor.set_wrap_width(Some(editor_width), cx) {
4105 snapshot = editor.snapshot(cx);
4106 }
4107
4108 let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
4109 let height = scroll_height
4110 .max(line_height)
4111 .min(line_height * max_lines as f32);
4112
4113 Some(size(width, height))
4114}