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