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