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