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