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