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