1use crate::{
2 blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
3 display_map::{
4 BlockContext, BlockStyle, DisplaySnapshot, HighlightedChunk, ToDisplayPoint, TransformBlock,
5 },
6 editor_settings::{
7 CurrentLineHighlight, DoubleClickInMultibuffer, MultiCursorModifier, ShowScrollbar,
8 },
9 git::{
10 blame::{CommitDetails, GitBlame},
11 diff_hunk_to_display, DisplayDiffHunk,
12 },
13 hover_popover::{
14 self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
15 },
16 hunk_status,
17 items::BufferSearchHighlights,
18 mouse_context_menu::{self, MouseContextMenu},
19 scroll::scroll_amount::ScrollAmount,
20 CodeActionsMenu, CursorShape, DisplayPoint, DisplayRow, DocumentHighlightRead,
21 DocumentHighlightWrite, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
22 ExpandExcerpts, GutterDimensions, HalfPageDown, HalfPageUp, HoveredCursor, HunkToExpand,
23 LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase,
24 Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
25};
26use client::ParticipantIndex;
27use collections::{BTreeMap, HashMap};
28use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
29use gpui::{
30 anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
31 transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
32 ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity,
33 FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
34 ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
35 ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
36 StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
37 ViewContext, WeakView, WindowContext,
38};
39use itertools::Itertools;
40use language::language_settings::{
41 IndentGuideBackgroundColoring, IndentGuideColoring, IndentGuideSettings, ShowWhitespaceSetting,
42};
43use lsp::DiagnosticSeverity;
44use multi_buffer::{Anchor, MultiBufferPoint, MultiBufferRow};
45use project::{
46 project_settings::{GitGutterSetting, ProjectSettings},
47 ProjectPath,
48};
49use settings::Settings;
50use smallvec::{smallvec, SmallVec};
51use std::{
52 any::TypeId,
53 borrow::Cow,
54 cmp::{self, Ordering},
55 fmt::{self, Write},
56 iter, mem,
57 ops::{Deref, Range},
58 sync::Arc,
59};
60use sum_tree::Bias;
61use theme::{ActiveTheme, PlayerColor};
62use ui::prelude::*;
63use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip};
64use util::ResultExt;
65use workspace::{item::Item, Workspace};
66
67struct SelectionLayout {
68 head: DisplayPoint,
69 cursor_shape: CursorShape,
70 is_newest: bool,
71 is_local: bool,
72 range: Range<DisplayPoint>,
73 active_rows: Range<DisplayRow>,
74 user_name: Option<SharedString>,
75}
76
77impl SelectionLayout {
78 fn new<T: ToPoint + ToDisplayPoint + Clone>(
79 selection: Selection<T>,
80 line_mode: bool,
81 cursor_shape: CursorShape,
82 map: &DisplaySnapshot,
83 is_newest: bool,
84 is_local: bool,
85 user_name: Option<SharedString>,
86 ) -> Self {
87 let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
88 let display_selection = point_selection.map(|p| p.to_display_point(map));
89 let mut range = display_selection.range();
90 let mut head = display_selection.head();
91 let mut active_rows = map.prev_line_boundary(point_selection.start).1.row()
92 ..map.next_line_boundary(point_selection.end).1.row();
93
94 // vim visual line mode
95 if line_mode {
96 let point_range = map.expand_to_line(point_selection.range());
97 range = point_range.start.to_display_point(map)..point_range.end.to_display_point(map);
98 }
99
100 // any vim visual mode (including line mode)
101 if (cursor_shape == CursorShape::Block || cursor_shape == CursorShape::Hollow)
102 && !range.is_empty()
103 && !selection.reversed
104 {
105 if head.column() > 0 {
106 head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left)
107 } else if head.row().0 > 0 && head != map.max_point() {
108 head = map.clip_point(
109 DisplayPoint::new(
110 head.row().previous_row(),
111 map.line_len(head.row().previous_row()),
112 ),
113 Bias::Left,
114 );
115 // updating range.end is a no-op unless you're cursor is
116 // on the newline containing a multi-buffer divider
117 // in which case the clip_point may have moved the head up
118 // an additional row.
119 range.end = DisplayPoint::new(head.row().next_row(), 0);
120 active_rows.end = head.row();
121 }
122 }
123
124 Self {
125 head,
126 cursor_shape,
127 is_newest,
128 is_local,
129 range,
130 active_rows,
131 user_name,
132 }
133 }
134}
135
136pub struct EditorElement {
137 editor: View<Editor>,
138 style: EditorStyle,
139}
140
141type DisplayRowDelta = u32;
142
143impl EditorElement {
144 pub(crate) const SCROLLBAR_WIDTH: Pixels = px(13.);
145
146 pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {
147 Self {
148 editor: editor.clone(),
149 style,
150 }
151 }
152
153 fn register_actions(&self, cx: &mut WindowContext) {
154 let view = &self.editor;
155 view.update(cx, |editor, cx| {
156 for action in editor.editor_actions.borrow().values() {
157 (action)(cx)
158 }
159 });
160
161 crate::rust_analyzer_ext::apply_related_actions(view, cx);
162 register_action(view, cx, Editor::move_left);
163 register_action(view, cx, Editor::move_right);
164 register_action(view, cx, Editor::move_down);
165 register_action(view, cx, Editor::move_down_by_lines);
166 register_action(view, cx, Editor::select_down_by_lines);
167 register_action(view, cx, Editor::move_up);
168 register_action(view, cx, Editor::move_up_by_lines);
169 register_action(view, cx, Editor::select_up_by_lines);
170 register_action(view, cx, Editor::select_page_down);
171 register_action(view, cx, Editor::select_page_up);
172 register_action(view, cx, Editor::cancel);
173 register_action(view, cx, Editor::newline);
174 register_action(view, cx, Editor::newline_above);
175 register_action(view, cx, Editor::newline_below);
176 register_action(view, cx, Editor::backspace);
177 register_action(view, cx, Editor::delete);
178 register_action(view, cx, Editor::tab);
179 register_action(view, cx, Editor::tab_prev);
180 register_action(view, cx, Editor::indent);
181 register_action(view, cx, Editor::outdent);
182 register_action(view, cx, Editor::delete_line);
183 register_action(view, cx, Editor::join_lines);
184 register_action(view, cx, Editor::sort_lines_case_sensitive);
185 register_action(view, cx, Editor::sort_lines_case_insensitive);
186 register_action(view, cx, Editor::reverse_lines);
187 register_action(view, cx, Editor::shuffle_lines);
188 register_action(view, cx, Editor::convert_to_upper_case);
189 register_action(view, cx, Editor::convert_to_lower_case);
190 register_action(view, cx, Editor::convert_to_title_case);
191 register_action(view, cx, Editor::convert_to_snake_case);
192 register_action(view, cx, Editor::convert_to_kebab_case);
193 register_action(view, cx, Editor::convert_to_upper_camel_case);
194 register_action(view, cx, Editor::convert_to_lower_camel_case);
195 register_action(view, cx, Editor::convert_to_opposite_case);
196 register_action(view, cx, Editor::delete_to_previous_word_start);
197 register_action(view, cx, Editor::delete_to_previous_subword_start);
198 register_action(view, cx, Editor::delete_to_next_word_end);
199 register_action(view, cx, Editor::delete_to_next_subword_end);
200 register_action(view, cx, Editor::delete_to_beginning_of_line);
201 register_action(view, cx, Editor::delete_to_end_of_line);
202 register_action(view, cx, Editor::cut_to_end_of_line);
203 register_action(view, cx, Editor::duplicate_line_up);
204 register_action(view, cx, Editor::duplicate_line_down);
205 register_action(view, cx, Editor::move_line_up);
206 register_action(view, cx, Editor::move_line_down);
207 register_action(view, cx, Editor::transpose);
208 register_action(view, cx, Editor::cut);
209 register_action(view, cx, Editor::copy);
210 register_action(view, cx, Editor::paste);
211 register_action(view, cx, Editor::undo);
212 register_action(view, cx, Editor::redo);
213 register_action(view, cx, Editor::move_page_up);
214 register_action(view, cx, Editor::move_page_down);
215 register_action(view, cx, Editor::next_screen);
216 register_action(view, cx, Editor::scroll_cursor_top);
217 register_action(view, cx, Editor::scroll_cursor_center);
218 register_action(view, cx, Editor::scroll_cursor_bottom);
219 register_action(view, cx, |editor, _: &LineDown, cx| {
220 editor.scroll_screen(&ScrollAmount::Line(1.), cx)
221 });
222 register_action(view, cx, |editor, _: &LineUp, cx| {
223 editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
224 });
225 register_action(view, cx, |editor, _: &HalfPageDown, cx| {
226 editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
227 });
228 register_action(view, cx, |editor, _: &HalfPageUp, cx| {
229 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
230 });
231 register_action(view, cx, |editor, _: &PageDown, cx| {
232 editor.scroll_screen(&ScrollAmount::Page(1.), cx)
233 });
234 register_action(view, cx, |editor, _: &PageUp, cx| {
235 editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
236 });
237 register_action(view, cx, Editor::move_to_previous_word_start);
238 register_action(view, cx, Editor::move_to_previous_subword_start);
239 register_action(view, cx, Editor::move_to_next_word_end);
240 register_action(view, cx, Editor::move_to_next_subword_end);
241 register_action(view, cx, Editor::move_to_beginning_of_line);
242 register_action(view, cx, Editor::move_to_end_of_line);
243 register_action(view, cx, Editor::move_to_start_of_paragraph);
244 register_action(view, cx, Editor::move_to_end_of_paragraph);
245 register_action(view, cx, Editor::move_to_beginning);
246 register_action(view, cx, Editor::move_to_end);
247 register_action(view, cx, Editor::select_up);
248 register_action(view, cx, Editor::select_down);
249 register_action(view, cx, Editor::select_left);
250 register_action(view, cx, Editor::select_right);
251 register_action(view, cx, Editor::select_to_previous_word_start);
252 register_action(view, cx, Editor::select_to_previous_subword_start);
253 register_action(view, cx, Editor::select_to_next_word_end);
254 register_action(view, cx, Editor::select_to_next_subword_end);
255 register_action(view, cx, Editor::select_to_beginning_of_line);
256 register_action(view, cx, Editor::select_to_end_of_line);
257 register_action(view, cx, Editor::select_to_start_of_paragraph);
258 register_action(view, cx, Editor::select_to_end_of_paragraph);
259 register_action(view, cx, Editor::select_to_beginning);
260 register_action(view, cx, Editor::select_to_end);
261 register_action(view, cx, Editor::select_all);
262 register_action(view, cx, |editor, action, cx| {
263 editor.select_all_matches(action, cx).log_err();
264 });
265 register_action(view, cx, Editor::select_line);
266 register_action(view, cx, Editor::split_selection_into_lines);
267 register_action(view, cx, Editor::add_selection_above);
268 register_action(view, cx, Editor::add_selection_below);
269 register_action(view, cx, |editor, action, cx| {
270 editor.select_next(action, cx).log_err();
271 });
272 register_action(view, cx, |editor, action, cx| {
273 editor.select_previous(action, cx).log_err();
274 });
275 register_action(view, cx, Editor::toggle_comments);
276 register_action(view, cx, Editor::select_larger_syntax_node);
277 register_action(view, cx, Editor::select_smaller_syntax_node);
278 register_action(view, cx, Editor::move_to_enclosing_bracket);
279 register_action(view, cx, Editor::undo_selection);
280 register_action(view, cx, Editor::redo_selection);
281 if !view.read(cx).is_singleton(cx) {
282 register_action(view, cx, Editor::expand_excerpts);
283 register_action(view, cx, Editor::expand_excerpts_up);
284 register_action(view, cx, Editor::expand_excerpts_down);
285 }
286 register_action(view, cx, Editor::go_to_diagnostic);
287 register_action(view, cx, Editor::go_to_prev_diagnostic);
288 register_action(view, cx, Editor::go_to_hunk);
289 register_action(view, cx, Editor::go_to_prev_hunk);
290 register_action(view, cx, |editor, a, cx| {
291 editor.go_to_definition(a, cx).detach_and_log_err(cx);
292 });
293 register_action(view, cx, |editor, a, cx| {
294 editor.go_to_definition_split(a, cx).detach_and_log_err(cx);
295 });
296 register_action(view, cx, |editor, a, cx| {
297 editor.go_to_implementation(a, cx).detach_and_log_err(cx);
298 });
299 register_action(view, cx, |editor, a, cx| {
300 editor
301 .go_to_implementation_split(a, cx)
302 .detach_and_log_err(cx);
303 });
304 register_action(view, cx, |editor, a, cx| {
305 editor.go_to_type_definition(a, cx).detach_and_log_err(cx);
306 });
307 register_action(view, cx, |editor, a, cx| {
308 editor
309 .go_to_type_definition_split(a, cx)
310 .detach_and_log_err(cx);
311 });
312 register_action(view, cx, Editor::open_url);
313 register_action(view, cx, Editor::fold);
314 register_action(view, cx, Editor::fold_at);
315 register_action(view, cx, Editor::unfold_lines);
316 register_action(view, cx, Editor::unfold_at);
317 register_action(view, cx, Editor::fold_selected_ranges);
318 register_action(view, cx, Editor::show_completions);
319 register_action(view, cx, Editor::toggle_code_actions);
320 register_action(view, cx, Editor::open_excerpts);
321 register_action(view, cx, Editor::open_excerpts_in_split);
322 register_action(view, cx, Editor::toggle_soft_wrap);
323 register_action(view, cx, Editor::toggle_tab_bar);
324 register_action(view, cx, Editor::toggle_line_numbers);
325 register_action(view, cx, Editor::toggle_indent_guides);
326 register_action(view, cx, Editor::toggle_inlay_hints);
327 register_action(view, cx, hover_popover::hover);
328 register_action(view, cx, Editor::reveal_in_finder);
329 register_action(view, cx, Editor::copy_path);
330 register_action(view, cx, Editor::copy_relative_path);
331 register_action(view, cx, Editor::copy_highlight_json);
332 register_action(view, cx, Editor::copy_permalink_to_line);
333 register_action(view, cx, Editor::open_permalink_to_line);
334 register_action(view, cx, Editor::toggle_git_blame);
335 register_action(view, cx, Editor::toggle_git_blame_inline);
336 register_action(view, cx, Editor::toggle_hunk_diff);
337 register_action(view, cx, Editor::expand_all_hunk_diffs);
338 register_action(view, cx, |editor, action, cx| {
339 if let Some(task) = editor.format(action, cx) {
340 task.detach_and_log_err(cx);
341 } else {
342 cx.propagate();
343 }
344 });
345 register_action(view, cx, Editor::restart_language_server);
346 register_action(view, cx, Editor::cancel_language_server_work);
347 register_action(view, cx, Editor::show_character_palette);
348 register_action(view, cx, |editor, action, cx| {
349 if let Some(task) = editor.confirm_completion(action, cx) {
350 task.detach_and_log_err(cx);
351 } else {
352 cx.propagate();
353 }
354 });
355 register_action(view, cx, |editor, action, cx| {
356 if let Some(task) = editor.confirm_code_action(action, cx) {
357 task.detach_and_log_err(cx);
358 } else {
359 cx.propagate();
360 }
361 });
362 register_action(view, cx, |editor, action, cx| {
363 if let Some(task) = editor.rename(action, cx) {
364 task.detach_and_log_err(cx);
365 } else {
366 cx.propagate();
367 }
368 });
369 register_action(view, cx, |editor, action, cx| {
370 if let Some(task) = editor.confirm_rename(action, cx) {
371 task.detach_and_log_err(cx);
372 } else {
373 cx.propagate();
374 }
375 });
376 register_action(view, cx, |editor, action, cx| {
377 if let Some(task) = editor.find_all_references(action, cx) {
378 task.detach_and_log_err(cx);
379 } else {
380 cx.propagate();
381 }
382 });
383 register_action(view, cx, Editor::next_inline_completion);
384 register_action(view, cx, Editor::previous_inline_completion);
385 register_action(view, cx, Editor::show_inline_completion);
386 register_action(view, cx, Editor::context_menu_first);
387 register_action(view, cx, Editor::context_menu_prev);
388 register_action(view, cx, Editor::context_menu_next);
389 register_action(view, cx, Editor::context_menu_last);
390 register_action(view, cx, Editor::display_cursor_names);
391 register_action(view, cx, Editor::unique_lines_case_insensitive);
392 register_action(view, cx, Editor::unique_lines_case_sensitive);
393 register_action(view, cx, Editor::accept_partial_inline_completion);
394 register_action(view, cx, Editor::accept_inline_completion);
395 register_action(view, cx, Editor::revert_selected_hunks);
396 register_action(view, cx, Editor::open_active_item_in_terminal)
397 }
398
399 fn register_key_listeners(&self, cx: &mut WindowContext, layout: &EditorLayout) {
400 let position_map = layout.position_map.clone();
401 cx.on_key_event({
402 let editor = self.editor.clone();
403 let text_hitbox = layout.text_hitbox.clone();
404 move |event: &ModifiersChangedEvent, phase, cx| {
405 if phase != DispatchPhase::Bubble {
406 return;
407 }
408
409 editor.update(cx, |editor, cx| {
410 Self::modifiers_changed(editor, event, &position_map, &text_hitbox, cx)
411 })
412 }
413 });
414 }
415
416 fn modifiers_changed(
417 editor: &mut Editor,
418 event: &ModifiersChangedEvent,
419 position_map: &PositionMap,
420 text_hitbox: &Hitbox,
421 cx: &mut ViewContext<Editor>,
422 ) {
423 let mouse_position = cx.mouse_position();
424 if !text_hitbox.is_hovered(cx) {
425 return;
426 }
427
428 editor.update_hovered_link(
429 position_map.point_for_position(text_hitbox.bounds, mouse_position),
430 &position_map.snapshot,
431 event.modifiers,
432 cx,
433 )
434 }
435
436 fn mouse_left_down(
437 editor: &mut Editor,
438 event: &MouseDownEvent,
439 hovered_hunk: Option<&HunkToExpand>,
440 position_map: &PositionMap,
441 text_hitbox: &Hitbox,
442 gutter_hitbox: &Hitbox,
443 cx: &mut ViewContext<Editor>,
444 ) {
445 if cx.default_prevented() {
446 return;
447 }
448
449 let mut click_count = event.click_count;
450 let mut modifiers = event.modifiers;
451
452 if let Some(hovered_hunk) = hovered_hunk {
453 editor.expand_diff_hunk(None, hovered_hunk, cx);
454 cx.notify();
455 return;
456 } else if gutter_hitbox.is_hovered(cx) {
457 click_count = 3; // Simulate triple-click when clicking the gutter to select lines
458 } else if !text_hitbox.is_hovered(cx) {
459 return;
460 }
461
462 if click_count == 2 && !editor.buffer().read(cx).is_singleton() {
463 match EditorSettings::get_global(cx).double_click_in_multibuffer {
464 DoubleClickInMultibuffer::Select => {
465 // do nothing special on double click, all selection logic is below
466 }
467 DoubleClickInMultibuffer::Open => {
468 if modifiers.alt {
469 // if double click is made with alt, pretend it's a regular double click without opening and alt,
470 // and run the selection logic.
471 modifiers.alt = false;
472 } else {
473 // if double click is made without alt, open the corresponding excerp
474 editor.open_excerpts(&OpenExcerpts, cx);
475 return;
476 }
477 }
478 }
479 }
480
481 let point_for_position =
482 position_map.point_for_position(text_hitbox.bounds, event.position);
483 let position = point_for_position.previous_valid;
484 if modifiers.shift && modifiers.alt {
485 editor.select(
486 SelectPhase::BeginColumnar {
487 position,
488 reset: false,
489 goal_column: point_for_position.exact_unclipped.column(),
490 },
491 cx,
492 );
493 } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.secondary()
494 {
495 editor.select(
496 SelectPhase::Extend {
497 position,
498 click_count,
499 },
500 cx,
501 );
502 } else {
503 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
504 let multi_cursor_modifier = match multi_cursor_setting {
505 MultiCursorModifier::Alt => modifiers.alt,
506 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
507 };
508 editor.select(
509 SelectPhase::Begin {
510 position,
511 add: multi_cursor_modifier,
512 click_count,
513 },
514 cx,
515 );
516 }
517
518 cx.stop_propagation();
519 }
520
521 fn mouse_right_down(
522 editor: &mut Editor,
523 event: &MouseDownEvent,
524 position_map: &PositionMap,
525 text_hitbox: &Hitbox,
526 cx: &mut ViewContext<Editor>,
527 ) {
528 if !text_hitbox.is_hovered(cx) {
529 return;
530 }
531 let point_for_position =
532 position_map.point_for_position(text_hitbox.bounds, event.position);
533 mouse_context_menu::deploy_context_menu(
534 editor,
535 event.position,
536 point_for_position.previous_valid,
537 cx,
538 );
539 cx.stop_propagation();
540 }
541
542 fn mouse_middle_down(
543 editor: &mut Editor,
544 event: &MouseDownEvent,
545 position_map: &PositionMap,
546 text_hitbox: &Hitbox,
547 cx: &mut ViewContext<Editor>,
548 ) {
549 if !text_hitbox.is_hovered(cx) || cx.default_prevented() {
550 return;
551 }
552
553 let point_for_position =
554 position_map.point_for_position(text_hitbox.bounds, event.position);
555 let position = point_for_position.previous_valid;
556
557 editor.select(
558 SelectPhase::BeginColumnar {
559 position,
560 reset: true,
561 goal_column: point_for_position.exact_unclipped.column(),
562 },
563 cx,
564 );
565 }
566
567 fn mouse_up(
568 editor: &mut Editor,
569 event: &MouseUpEvent,
570 position_map: &PositionMap,
571 text_hitbox: &Hitbox,
572 cx: &mut ViewContext<Editor>,
573 ) {
574 let end_selection = editor.has_pending_selection();
575 let pending_nonempty_selections = editor.has_pending_nonempty_selection();
576
577 if end_selection {
578 editor.select(SelectPhase::End, cx);
579 }
580
581 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
582 let multi_cursor_modifier = match multi_cursor_setting {
583 MultiCursorModifier::Alt => event.modifiers.secondary(),
584 MultiCursorModifier::CmdOrCtrl => event.modifiers.alt,
585 };
586
587 if !pending_nonempty_selections && multi_cursor_modifier && text_hitbox.is_hovered(cx) {
588 let point = position_map.point_for_position(text_hitbox.bounds, event.position);
589 editor.handle_click_hovered_link(point, event.modifiers, cx);
590
591 cx.stop_propagation();
592 } else if end_selection && pending_nonempty_selections {
593 cx.stop_propagation();
594 } else if cfg!(target_os = "linux") && event.button == MouseButton::Middle {
595 if !text_hitbox.is_hovered(cx) || editor.read_only(cx) {
596 return;
597 }
598
599 #[cfg(target_os = "linux")]
600 if let Some(item) = cx.read_from_primary() {
601 let point_for_position =
602 position_map.point_for_position(text_hitbox.bounds, event.position);
603 let position = point_for_position.previous_valid;
604
605 editor.select(
606 SelectPhase::Begin {
607 position,
608 add: false,
609 click_count: 1,
610 },
611 cx,
612 );
613 editor.insert(item.text(), cx);
614 }
615 cx.stop_propagation()
616 }
617 }
618
619 fn mouse_dragged(
620 editor: &mut Editor,
621 event: &MouseMoveEvent,
622 position_map: &PositionMap,
623 text_bounds: Bounds<Pixels>,
624 cx: &mut ViewContext<Editor>,
625 ) {
626 if !editor.has_pending_selection() {
627 return;
628 }
629
630 let point_for_position = position_map.point_for_position(text_bounds, event.position);
631 let mut scroll_delta = gpui::Point::<f32>::default();
632 let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
633 let top = text_bounds.origin.y + vertical_margin;
634 let bottom = text_bounds.lower_left().y - vertical_margin;
635 if event.position.y < top {
636 scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y);
637 }
638 if event.position.y > bottom {
639 scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
640 }
641
642 let horizontal_margin = position_map.line_height.min(text_bounds.size.width / 3.0);
643 let left = text_bounds.origin.x + horizontal_margin;
644 let right = text_bounds.upper_right().x - horizontal_margin;
645 if event.position.x < left {
646 scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
647 }
648 if event.position.x > right {
649 scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
650 }
651
652 editor.select(
653 SelectPhase::Update {
654 position: point_for_position.previous_valid,
655 goal_column: point_for_position.exact_unclipped.column(),
656 scroll_delta,
657 },
658 cx,
659 );
660 }
661
662 fn mouse_moved(
663 editor: &mut Editor,
664 event: &MouseMoveEvent,
665 position_map: &PositionMap,
666 text_hitbox: &Hitbox,
667 gutter_hitbox: &Hitbox,
668 cx: &mut ViewContext<Editor>,
669 ) {
670 let modifiers = event.modifiers;
671 let gutter_hovered = gutter_hitbox.is_hovered(cx);
672 editor.set_gutter_hovered(gutter_hovered, cx);
673
674 // Don't trigger hover popover if mouse is hovering over context menu
675 if text_hitbox.is_hovered(cx) {
676 let point_for_position =
677 position_map.point_for_position(text_hitbox.bounds, event.position);
678
679 editor.update_hovered_link(point_for_position, &position_map.snapshot, modifiers, cx);
680
681 if let Some(point) = point_for_position.as_valid() {
682 let anchor = position_map
683 .snapshot
684 .buffer_snapshot
685 .anchor_before(point.to_offset(&position_map.snapshot, Bias::Left));
686 hover_at(editor, Some(anchor), cx);
687 Self::update_visible_cursor(editor, point, position_map, cx);
688 } else {
689 hover_at(editor, None, cx);
690 }
691 } else {
692 editor.hide_hovered_link(cx);
693 hover_at(editor, None, cx);
694 if gutter_hovered {
695 cx.stop_propagation();
696 }
697 }
698 }
699
700 fn update_visible_cursor(
701 editor: &mut Editor,
702 point: DisplayPoint,
703 position_map: &PositionMap,
704 cx: &mut ViewContext<Editor>,
705 ) {
706 let snapshot = &position_map.snapshot;
707 let Some(hub) = editor.collaboration_hub() else {
708 return;
709 };
710 let range = DisplayPoint::new(point.row(), point.column().saturating_sub(1))
711 ..DisplayPoint::new(
712 point.row(),
713 (point.column() + 1).min(snapshot.line_len(point.row())),
714 );
715
716 let range = snapshot
717 .buffer_snapshot
718 .anchor_at(range.start.to_point(&snapshot.display_snapshot), Bias::Left)
719 ..snapshot
720 .buffer_snapshot
721 .anchor_at(range.end.to_point(&snapshot.display_snapshot), Bias::Right);
722
723 let Some(selection) = snapshot.remote_selections_in_range(&range, hub, cx).next() else {
724 return;
725 };
726 let key = crate::HoveredCursor {
727 replica_id: selection.replica_id,
728 selection_id: selection.selection.id,
729 };
730 editor.hovered_cursors.insert(
731 key.clone(),
732 cx.spawn(|editor, mut cx| async move {
733 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
734 editor
735 .update(&mut cx, |editor, cx| {
736 editor.hovered_cursors.remove(&key);
737 cx.notify();
738 })
739 .ok();
740 }),
741 );
742 cx.notify()
743 }
744
745 fn layout_selections(
746 &self,
747 start_anchor: Anchor,
748 end_anchor: Anchor,
749 snapshot: &EditorSnapshot,
750 start_row: DisplayRow,
751 end_row: DisplayRow,
752 cx: &mut WindowContext,
753 ) -> (
754 Vec<(PlayerColor, Vec<SelectionLayout>)>,
755 BTreeMap<DisplayRow, bool>,
756 Option<DisplayPoint>,
757 ) {
758 let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
759 let mut active_rows = BTreeMap::new();
760 let mut newest_selection_head = None;
761 let editor = self.editor.read(cx);
762
763 if editor.show_local_selections {
764 let mut local_selections: Vec<Selection<Point>> = editor
765 .selections
766 .disjoint_in_range(start_anchor..end_anchor, cx);
767 local_selections.extend(editor.selections.pending(cx));
768 let mut layouts = Vec::new();
769 let newest = editor.selections.newest(cx);
770 for selection in local_selections.drain(..) {
771 let is_empty = selection.start == selection.end;
772 let is_newest = selection == newest;
773
774 let layout = SelectionLayout::new(
775 selection,
776 editor.selections.line_mode,
777 editor.cursor_shape,
778 &snapshot.display_snapshot,
779 is_newest,
780 editor.leader_peer_id.is_none(),
781 None,
782 );
783 if is_newest {
784 newest_selection_head = Some(layout.head);
785 }
786
787 for row in cmp::max(layout.active_rows.start.0, start_row.0)
788 ..=cmp::min(layout.active_rows.end.0, end_row.0)
789 {
790 let contains_non_empty_selection =
791 active_rows.entry(DisplayRow(row)).or_insert(!is_empty);
792 *contains_non_empty_selection |= !is_empty;
793 }
794 layouts.push(layout);
795 }
796
797 let player = if editor.read_only(cx) {
798 cx.theme().players().read_only()
799 } else {
800 self.style.local_player
801 };
802
803 selections.push((player, layouts));
804 }
805
806 if let Some(collaboration_hub) = &editor.collaboration_hub {
807 // When following someone, render the local selections in their color.
808 if let Some(leader_id) = editor.leader_peer_id {
809 if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
810 if let Some(participant_index) = collaboration_hub
811 .user_participant_indices(cx)
812 .get(&collaborator.user_id)
813 {
814 if let Some((local_selection_style, _)) = selections.first_mut() {
815 *local_selection_style = cx
816 .theme()
817 .players()
818 .color_for_participant(participant_index.0);
819 }
820 }
821 }
822 }
823
824 let mut remote_selections = HashMap::default();
825 for selection in snapshot.remote_selections_in_range(
826 &(start_anchor..end_anchor),
827 collaboration_hub.as_ref(),
828 cx,
829 ) {
830 let selection_style = Self::get_participant_color(selection.participant_index, cx);
831
832 // Don't re-render the leader's selections, since the local selections
833 // match theirs.
834 if Some(selection.peer_id) == editor.leader_peer_id {
835 continue;
836 }
837 let key = HoveredCursor {
838 replica_id: selection.replica_id,
839 selection_id: selection.selection.id,
840 };
841
842 let is_shown =
843 editor.show_cursor_names || editor.hovered_cursors.contains_key(&key);
844
845 remote_selections
846 .entry(selection.replica_id)
847 .or_insert((selection_style, Vec::new()))
848 .1
849 .push(SelectionLayout::new(
850 selection.selection,
851 selection.line_mode,
852 selection.cursor_shape,
853 &snapshot.display_snapshot,
854 false,
855 false,
856 if is_shown { selection.user_name } else { None },
857 ));
858 }
859
860 selections.extend(remote_selections.into_values());
861 }
862 (selections, active_rows, newest_selection_head)
863 }
864
865 fn collect_cursors(
866 &self,
867 snapshot: &EditorSnapshot,
868 cx: &mut WindowContext,
869 ) -> Vec<(DisplayPoint, Hsla)> {
870 let editor = self.editor.read(cx);
871 let mut cursors = Vec::new();
872 let mut skip_local = false;
873 let mut add_cursor = |anchor: Anchor, color| {
874 cursors.push((anchor.to_display_point(&snapshot.display_snapshot), color));
875 };
876 // Remote cursors
877 if let Some(collaboration_hub) = &editor.collaboration_hub {
878 for remote_selection in snapshot.remote_selections_in_range(
879 &(Anchor::min()..Anchor::max()),
880 collaboration_hub.deref(),
881 cx,
882 ) {
883 let color = Self::get_participant_color(remote_selection.participant_index, cx);
884 add_cursor(remote_selection.selection.head(), color.cursor);
885 if Some(remote_selection.peer_id) == editor.leader_peer_id {
886 skip_local = true;
887 }
888 }
889 }
890 // Local cursors
891 if !skip_local {
892 let color = cx.theme().players().local().cursor;
893 editor.selections.disjoint.iter().for_each(|selection| {
894 add_cursor(selection.head(), color);
895 });
896 if let Some(ref selection) = editor.selections.pending_anchor() {
897 add_cursor(selection.head(), color);
898 }
899 }
900 cursors
901 }
902
903 #[allow(clippy::too_many_arguments)]
904 fn layout_visible_cursors(
905 &self,
906 snapshot: &EditorSnapshot,
907 selections: &[(PlayerColor, Vec<SelectionLayout>)],
908 visible_display_row_range: Range<DisplayRow>,
909 line_layouts: &[LineWithInvisibles],
910 text_hitbox: &Hitbox,
911 content_origin: gpui::Point<Pixels>,
912 scroll_position: gpui::Point<f32>,
913 scroll_pixel_position: gpui::Point<Pixels>,
914 line_height: Pixels,
915 em_width: Pixels,
916 autoscroll_containing_element: bool,
917 cx: &mut WindowContext,
918 ) -> Vec<CursorLayout> {
919 let mut autoscroll_bounds = None;
920 let cursor_layouts = self.editor.update(cx, |editor, cx| {
921 let mut cursors = Vec::new();
922 for (player_color, selections) in selections {
923 for selection in selections {
924 let cursor_position = selection.head;
925
926 let in_range = visible_display_row_range.contains(&cursor_position.row());
927 if (selection.is_local && !editor.show_local_cursors(cx)) || !in_range {
928 continue;
929 }
930
931 let cursor_row_layout = &line_layouts
932 [cursor_position.row().minus(visible_display_row_range.start) as usize];
933 let cursor_column = cursor_position.column() as usize;
934
935 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
936 let mut block_width =
937 cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
938 if block_width == Pixels::ZERO {
939 block_width = em_width;
940 }
941 let block_text = if let CursorShape::Block = selection.cursor_shape {
942 snapshot.display_chars_at(cursor_position).next().and_then(
943 |(character, _)| {
944 let text = if character == '\n' {
945 SharedString::from(" ")
946 } else {
947 SharedString::from(character.to_string())
948 };
949 let len = text.len();
950
951 let font = cursor_row_layout
952 .font_id_for_index(cursor_column)
953 .and_then(|cursor_font_id| {
954 cx.text_system().get_font_for_id(cursor_font_id)
955 })
956 .unwrap_or(self.style.text.font());
957
958 cx.text_system()
959 .shape_line(
960 text,
961 cursor_row_layout.font_size,
962 &[TextRun {
963 len,
964 font,
965 color: self.style.background,
966 background_color: None,
967 strikethrough: None,
968 underline: None,
969 }],
970 )
971 .log_err()
972 },
973 )
974 } else {
975 None
976 };
977
978 let x = cursor_character_x - scroll_pixel_position.x;
979 let y = (cursor_position.row().as_f32()
980 - scroll_pixel_position.y / line_height)
981 * line_height;
982 if selection.is_newest {
983 editor.pixel_position_of_newest_cursor = Some(point(
984 text_hitbox.origin.x + x + block_width / 2.,
985 text_hitbox.origin.y + y + line_height / 2.,
986 ));
987
988 if autoscroll_containing_element {
989 let top = text_hitbox.origin.y
990 + (cursor_position.row().as_f32() - scroll_position.y - 3.).max(0.)
991 * line_height;
992 let left = text_hitbox.origin.x
993 + (cursor_position.column() as f32 - scroll_position.x - 3.)
994 .max(0.)
995 * em_width;
996
997 let bottom = text_hitbox.origin.y
998 + (cursor_position.row().as_f32() - scroll_position.y + 4.)
999 * line_height;
1000 let right = text_hitbox.origin.x
1001 + (cursor_position.column() as f32 - scroll_position.x + 4.)
1002 * em_width;
1003
1004 autoscroll_bounds =
1005 Some(Bounds::from_corners(point(left, top), point(right, bottom)))
1006 }
1007 }
1008
1009 let mut cursor = CursorLayout {
1010 color: player_color.cursor,
1011 block_width,
1012 origin: point(x, y),
1013 line_height,
1014 shape: selection.cursor_shape,
1015 block_text,
1016 cursor_name: None,
1017 };
1018 let cursor_name = selection.user_name.clone().map(|name| CursorName {
1019 string: name,
1020 color: self.style.background,
1021 is_top_row: cursor_position.row().0 == 0,
1022 });
1023 cursor.layout(content_origin, cursor_name, cx);
1024 cursors.push(cursor);
1025 }
1026 }
1027 cursors
1028 });
1029
1030 if let Some(bounds) = autoscroll_bounds {
1031 cx.request_autoscroll(bounds);
1032 }
1033
1034 cursor_layouts
1035 }
1036
1037 fn layout_scrollbar(
1038 &self,
1039 snapshot: &EditorSnapshot,
1040 bounds: Bounds<Pixels>,
1041 scroll_position: gpui::Point<f32>,
1042 rows_per_page: f32,
1043 non_visible_cursors: bool,
1044 cx: &mut WindowContext,
1045 ) -> Option<ScrollbarLayout> {
1046 let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
1047 let show_scrollbars = match scrollbar_settings.show {
1048 ShowScrollbar::Auto => {
1049 let editor = self.editor.read(cx);
1050 let is_singleton = editor.is_singleton(cx);
1051 // Git
1052 (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
1053 ||
1054 // Buffer Search Results
1055 (is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
1056 ||
1057 // Selected Symbol Occurrences
1058 (is_singleton && scrollbar_settings.selected_symbol && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
1059 ||
1060 // Diagnostics
1061 (is_singleton && scrollbar_settings.diagnostics && snapshot.buffer_snapshot.has_diagnostics())
1062 ||
1063 // Cursors out of sight
1064 non_visible_cursors
1065 ||
1066 // Scrollmanager
1067 editor.scroll_manager.scrollbars_visible()
1068 }
1069 ShowScrollbar::System => self.editor.read(cx).scroll_manager.scrollbars_visible(),
1070 ShowScrollbar::Always => true,
1071 ShowScrollbar::Never => false,
1072 };
1073 if snapshot.mode != EditorMode::Full {
1074 return None;
1075 }
1076
1077 let visible_row_range = scroll_position.y..scroll_position.y + rows_per_page;
1078
1079 // If a drag took place after we started dragging the scrollbar,
1080 // cancel the scrollbar drag.
1081 if cx.has_active_drag() {
1082 self.editor.update(cx, |editor, cx| {
1083 editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
1084 });
1085 }
1086
1087 let track_bounds = Bounds::from_corners(
1088 point(self.scrollbar_left(&bounds), bounds.origin.y),
1089 point(bounds.lower_right().x, bounds.lower_left().y),
1090 );
1091
1092 let height = bounds.size.height;
1093 let total_rows = snapshot.max_point().row().as_f32() + rows_per_page;
1094 let px_per_row = height / total_rows;
1095 let thumb_height = (rows_per_page * px_per_row).max(ScrollbarLayout::MIN_THUMB_HEIGHT);
1096 let row_height = (height - thumb_height) / snapshot.max_point().row().as_f32();
1097
1098 Some(ScrollbarLayout {
1099 hitbox: cx.insert_hitbox(track_bounds, false),
1100 visible_row_range,
1101 row_height,
1102 visible: show_scrollbars,
1103 thumb_height,
1104 })
1105 }
1106
1107 #[allow(clippy::too_many_arguments)]
1108 fn prepaint_gutter_fold_toggles(
1109 &self,
1110 toggles: &mut [Option<AnyElement>],
1111 line_height: Pixels,
1112 gutter_dimensions: &GutterDimensions,
1113 gutter_settings: crate::editor_settings::Gutter,
1114 scroll_pixel_position: gpui::Point<Pixels>,
1115 gutter_hitbox: &Hitbox,
1116 cx: &mut WindowContext,
1117 ) {
1118 for (ix, fold_indicator) in toggles.iter_mut().enumerate() {
1119 if let Some(fold_indicator) = fold_indicator {
1120 debug_assert!(gutter_settings.folds);
1121 let available_space = size(
1122 AvailableSpace::MinContent,
1123 AvailableSpace::Definite(line_height * 0.55),
1124 );
1125 let fold_indicator_size = fold_indicator.layout_as_root(available_space, cx);
1126
1127 let position = point(
1128 gutter_dimensions.width - gutter_dimensions.right_padding,
1129 ix as f32 * line_height - (scroll_pixel_position.y % line_height),
1130 );
1131 let centering_offset = point(
1132 (gutter_dimensions.fold_area_width() - fold_indicator_size.width) / 2.,
1133 (line_height - fold_indicator_size.height) / 2.,
1134 );
1135 let origin = gutter_hitbox.origin + position + centering_offset;
1136 fold_indicator.prepaint_as_root(origin, available_space, cx);
1137 }
1138 }
1139 }
1140
1141 #[allow(clippy::too_many_arguments)]
1142 fn prepaint_crease_trailers(
1143 &self,
1144 trailers: Vec<Option<AnyElement>>,
1145 lines: &[LineWithInvisibles],
1146 line_height: Pixels,
1147 content_origin: gpui::Point<Pixels>,
1148 scroll_pixel_position: gpui::Point<Pixels>,
1149 em_width: Pixels,
1150 cx: &mut WindowContext,
1151 ) -> Vec<Option<CreaseTrailerLayout>> {
1152 trailers
1153 .into_iter()
1154 .enumerate()
1155 .map(|(ix, element)| {
1156 let mut element = element?;
1157 let available_space = size(
1158 AvailableSpace::MinContent,
1159 AvailableSpace::Definite(line_height),
1160 );
1161 let size = element.layout_as_root(available_space, cx);
1162
1163 let line = &lines[ix];
1164 let padding = if line.width == Pixels::ZERO {
1165 Pixels::ZERO
1166 } else {
1167 4. * em_width
1168 };
1169 let position = point(
1170 scroll_pixel_position.x + line.width + padding,
1171 ix as f32 * line_height - (scroll_pixel_position.y % line_height),
1172 );
1173 let centering_offset = point(px(0.), (line_height - size.height) / 2.);
1174 let origin = content_origin + position + centering_offset;
1175 element.prepaint_as_root(origin, available_space, cx);
1176 Some(CreaseTrailerLayout {
1177 element,
1178 bounds: Bounds::new(origin, size),
1179 })
1180 })
1181 .collect()
1182 }
1183
1184 // Folds contained in a hunk are ignored apart from shrinking visual size
1185 // If a fold contains any hunks then that fold line is marked as modified
1186 fn layout_git_gutters(
1187 &self,
1188 line_height: Pixels,
1189 gutter_hitbox: &Hitbox,
1190 display_rows: Range<DisplayRow>,
1191 snapshot: &EditorSnapshot,
1192 cx: &mut WindowContext,
1193 ) -> Vec<(DisplayDiffHunk, Option<Hitbox>)> {
1194 let buffer_snapshot = &snapshot.buffer_snapshot;
1195
1196 let buffer_start_row = MultiBufferRow(
1197 DisplayPoint::new(display_rows.start, 0)
1198 .to_point(snapshot)
1199 .row,
1200 );
1201 let buffer_end_row = MultiBufferRow(
1202 DisplayPoint::new(display_rows.end, 0)
1203 .to_point(snapshot)
1204 .row,
1205 );
1206
1207 let expanded_hunk_display_rows = self.editor.update(cx, |editor, _| {
1208 editor
1209 .expanded_hunks
1210 .hunks(false)
1211 .map(|expanded_hunk| {
1212 let start_row = expanded_hunk
1213 .hunk_range
1214 .start
1215 .to_display_point(snapshot)
1216 .row();
1217 let end_row = expanded_hunk
1218 .hunk_range
1219 .end
1220 .to_display_point(snapshot)
1221 .row();
1222 (start_row, end_row)
1223 })
1224 .collect::<HashMap<_, _>>()
1225 });
1226
1227 let git_gutter_setting = ProjectSettings::get_global(cx)
1228 .git
1229 .git_gutter
1230 .unwrap_or_default();
1231 buffer_snapshot
1232 .git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
1233 .map(|hunk| diff_hunk_to_display(&hunk, snapshot))
1234 .dedup()
1235 .map(|hunk| match git_gutter_setting {
1236 GitGutterSetting::TrackedFiles => {
1237 let hitbox = if let DisplayDiffHunk::Unfolded {
1238 display_row_range, ..
1239 } = &hunk
1240 {
1241 let was_expanded = expanded_hunk_display_rows
1242 .get(&display_row_range.start)
1243 .map(|expanded_end_row| expanded_end_row == &display_row_range.end)
1244 .unwrap_or(false);
1245 if was_expanded {
1246 None
1247 } else {
1248 let hunk_bounds = Self::diff_hunk_bounds(
1249 &snapshot,
1250 line_height,
1251 gutter_hitbox.bounds,
1252 &hunk,
1253 );
1254 Some(cx.insert_hitbox(hunk_bounds, true))
1255 }
1256 } else {
1257 None
1258 };
1259 (hunk, hitbox)
1260 }
1261 GitGutterSetting::Hide => (hunk, None),
1262 })
1263 .collect()
1264 }
1265
1266 #[allow(clippy::too_many_arguments)]
1267 fn layout_inline_blame(
1268 &self,
1269 display_row: DisplayRow,
1270 display_snapshot: &DisplaySnapshot,
1271 line_layout: &LineWithInvisibles,
1272 crease_trailer: Option<&CreaseTrailerLayout>,
1273 em_width: Pixels,
1274 content_origin: gpui::Point<Pixels>,
1275 scroll_pixel_position: gpui::Point<Pixels>,
1276 line_height: Pixels,
1277 cx: &mut WindowContext,
1278 ) -> Option<AnyElement> {
1279 if !self
1280 .editor
1281 .update(cx, |editor, cx| editor.render_git_blame_inline(cx))
1282 {
1283 return None;
1284 }
1285
1286 let workspace = self
1287 .editor
1288 .read(cx)
1289 .workspace
1290 .as_ref()
1291 .map(|(w, _)| w.clone());
1292
1293 let display_point = DisplayPoint::new(display_row, 0);
1294 let buffer_row = MultiBufferRow(display_point.to_point(display_snapshot).row);
1295
1296 let blame = self.editor.read(cx).blame.clone()?;
1297 let blame_entry = blame
1298 .update(cx, |blame, cx| {
1299 blame.blame_for_rows([Some(buffer_row)], cx).next()
1300 })
1301 .flatten()?;
1302
1303 let mut element =
1304 render_inline_blame_entry(&blame, blame_entry, &self.style, workspace, cx);
1305
1306 let start_y = content_origin.y
1307 + line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height);
1308
1309 let start_x = {
1310 const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
1311
1312 let line_end = if let Some(crease_trailer) = crease_trailer {
1313 crease_trailer.bounds.right()
1314 } else {
1315 content_origin.x - scroll_pixel_position.x + line_layout.width
1316 };
1317 let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS;
1318
1319 let min_column_in_pixels = ProjectSettings::get_global(cx)
1320 .git
1321 .inline_blame
1322 .and_then(|settings| settings.min_column)
1323 .map(|col| self.column_pixels(col as usize, cx))
1324 .unwrap_or(px(0.));
1325 let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels;
1326
1327 cmp::max(padded_line_end, min_start)
1328 };
1329
1330 let absolute_offset = point(start_x, start_y);
1331 let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
1332
1333 element.prepaint_as_root(absolute_offset, available_space, cx);
1334
1335 Some(element)
1336 }
1337
1338 #[allow(clippy::too_many_arguments)]
1339 fn layout_blame_entries(
1340 &self,
1341 buffer_rows: impl Iterator<Item = Option<MultiBufferRow>>,
1342 em_width: Pixels,
1343 scroll_position: gpui::Point<f32>,
1344 line_height: Pixels,
1345 gutter_hitbox: &Hitbox,
1346 max_width: Option<Pixels>,
1347 cx: &mut WindowContext,
1348 ) -> Option<Vec<AnyElement>> {
1349 if !self
1350 .editor
1351 .update(cx, |editor, cx| editor.render_git_blame_gutter(cx))
1352 {
1353 return None;
1354 }
1355
1356 let blame = self.editor.read(cx).blame.clone()?;
1357 let blamed_rows: Vec<_> = blame.update(cx, |blame, cx| {
1358 blame.blame_for_rows(buffer_rows, cx).collect()
1359 });
1360
1361 let width = if let Some(max_width) = max_width {
1362 AvailableSpace::Definite(max_width)
1363 } else {
1364 AvailableSpace::MaxContent
1365 };
1366 let scroll_top = scroll_position.y * line_height;
1367 let start_x = em_width * 1;
1368
1369 let mut last_used_color: Option<(PlayerColor, Oid)> = None;
1370
1371 let shaped_lines = blamed_rows
1372 .into_iter()
1373 .enumerate()
1374 .flat_map(|(ix, blame_entry)| {
1375 if let Some(blame_entry) = blame_entry {
1376 let mut element = render_blame_entry(
1377 ix,
1378 &blame,
1379 blame_entry,
1380 &self.style,
1381 &mut last_used_color,
1382 self.editor.clone(),
1383 cx,
1384 );
1385
1386 let start_y = ix as f32 * line_height - (scroll_top % line_height);
1387 let absolute_offset = gutter_hitbox.origin + point(start_x, start_y);
1388
1389 element.prepaint_as_root(
1390 absolute_offset,
1391 size(width, AvailableSpace::MinContent),
1392 cx,
1393 );
1394
1395 Some(element)
1396 } else {
1397 None
1398 }
1399 })
1400 .collect();
1401
1402 Some(shaped_lines)
1403 }
1404
1405 #[allow(clippy::too_many_arguments)]
1406 fn layout_indent_guides(
1407 &self,
1408 content_origin: gpui::Point<Pixels>,
1409 text_origin: gpui::Point<Pixels>,
1410 visible_buffer_range: Range<MultiBufferRow>,
1411 scroll_pixel_position: gpui::Point<Pixels>,
1412 line_height: Pixels,
1413 snapshot: &DisplaySnapshot,
1414 cx: &mut WindowContext,
1415 ) -> Option<Vec<IndentGuideLayout>> {
1416 let indent_guides = self.editor.update(cx, |editor, cx| {
1417 editor.indent_guides(visible_buffer_range, snapshot, cx)
1418 })?;
1419
1420 let active_indent_guide_indices = self.editor.update(cx, |editor, cx| {
1421 editor
1422 .find_active_indent_guide_indices(&indent_guides, snapshot, cx)
1423 .unwrap_or_default()
1424 });
1425
1426 Some(
1427 indent_guides
1428 .into_iter()
1429 .enumerate()
1430 .filter_map(|(i, indent_guide)| {
1431 let single_indent_width =
1432 self.column_pixels(indent_guide.tab_size as usize, cx);
1433 let total_width = single_indent_width * indent_guide.depth as f32;
1434 let start_x = content_origin.x + total_width - scroll_pixel_position.x;
1435 if start_x >= text_origin.x {
1436 let (offset_y, length) = Self::calculate_indent_guide_bounds(
1437 indent_guide.multibuffer_row_range.clone(),
1438 line_height,
1439 snapshot,
1440 );
1441
1442 let start_y = content_origin.y + offset_y - scroll_pixel_position.y;
1443
1444 Some(IndentGuideLayout {
1445 origin: point(start_x, start_y),
1446 length,
1447 single_indent_width,
1448 depth: indent_guide.depth,
1449 active: active_indent_guide_indices.contains(&i),
1450 settings: indent_guide.settings,
1451 })
1452 } else {
1453 None
1454 }
1455 })
1456 .collect(),
1457 )
1458 }
1459
1460 fn calculate_indent_guide_bounds(
1461 row_range: Range<MultiBufferRow>,
1462 line_height: Pixels,
1463 snapshot: &DisplaySnapshot,
1464 ) -> (gpui::Pixels, gpui::Pixels) {
1465 let start_point = Point::new(row_range.start.0, 0);
1466 let end_point = Point::new(row_range.end.0, 0);
1467
1468 let row_range = start_point.to_display_point(snapshot).row()
1469 ..end_point.to_display_point(snapshot).row();
1470
1471 let mut prev_line = start_point;
1472 prev_line.row = prev_line.row.saturating_sub(1);
1473 let prev_line = prev_line.to_display_point(snapshot).row();
1474
1475 let mut cons_line = end_point;
1476 cons_line.row += 1;
1477 let cons_line = cons_line.to_display_point(snapshot).row();
1478
1479 let mut offset_y = row_range.start.0 as f32 * line_height;
1480 let mut length = (cons_line.0.saturating_sub(row_range.start.0)) as f32 * line_height;
1481
1482 // If we are at the end of the buffer, ensure that the indent guide extends to the end of the line.
1483 if row_range.end == cons_line {
1484 length += line_height;
1485 }
1486
1487 // If there is a block (e.g. diagnostic) in between the start of the indent guide and the line above,
1488 // we want to extend the indent guide to the start of the block.
1489 let mut block_height = 0;
1490 let mut block_offset = 0;
1491 let mut found_excerpt_header = false;
1492 for (_, block) in snapshot.blocks_in_range(prev_line..row_range.start) {
1493 if matches!(block, TransformBlock::ExcerptHeader { .. }) {
1494 found_excerpt_header = true;
1495 break;
1496 }
1497 block_offset += block.height();
1498 block_height += block.height();
1499 }
1500 if !found_excerpt_header {
1501 offset_y -= block_offset as f32 * line_height;
1502 length += block_height as f32 * line_height;
1503 }
1504
1505 // If there is a block (e.g. diagnostic) at the end of an multibuffer excerpt,
1506 // we want to ensure that the indent guide stops before the excerpt header.
1507 let mut block_height = 0;
1508 let mut found_excerpt_header = false;
1509 for (_, block) in snapshot.blocks_in_range(row_range.end..cons_line) {
1510 if matches!(block, TransformBlock::ExcerptHeader { .. }) {
1511 found_excerpt_header = true;
1512 }
1513 block_height += block.height();
1514 }
1515 if found_excerpt_header {
1516 length -= block_height as f32 * line_height;
1517 }
1518
1519 (offset_y, length)
1520 }
1521
1522 fn layout_run_indicators(
1523 &self,
1524 line_height: Pixels,
1525 scroll_pixel_position: gpui::Point<Pixels>,
1526 gutter_dimensions: &GutterDimensions,
1527 gutter_hitbox: &Hitbox,
1528 snapshot: &EditorSnapshot,
1529 cx: &mut WindowContext,
1530 ) -> Vec<AnyElement> {
1531 self.editor.update(cx, |editor, cx| {
1532 let active_task_indicator_row =
1533 if let Some(crate::ContextMenu::CodeActions(CodeActionsMenu {
1534 deployed_from_indicator,
1535 actions,
1536 ..
1537 })) = editor.context_menu.read().as_ref()
1538 {
1539 actions
1540 .tasks
1541 .as_ref()
1542 .map(|tasks| tasks.position.to_display_point(snapshot).row())
1543 .or_else(|| *deployed_from_indicator)
1544 } else {
1545 None
1546 };
1547 editor
1548 .tasks
1549 .iter()
1550 .filter_map(|(_, tasks)| {
1551 let multibuffer_point = tasks.offset.0.to_point(&snapshot.buffer_snapshot);
1552 let multibuffer_row = MultiBufferRow(multibuffer_point.row);
1553 if snapshot.is_line_folded(multibuffer_row) {
1554 return None;
1555 }
1556 let display_row = multibuffer_point.to_display_point(snapshot).row();
1557 let button = editor.render_run_indicator(
1558 &self.style,
1559 Some(display_row) == active_task_indicator_row,
1560 display_row,
1561 cx,
1562 );
1563
1564 let button = prepaint_gutter_button(
1565 button,
1566 display_row,
1567 line_height,
1568 gutter_dimensions,
1569 scroll_pixel_position,
1570 gutter_hitbox,
1571 cx,
1572 );
1573 Some(button)
1574 })
1575 .collect_vec()
1576 })
1577 }
1578
1579 fn layout_code_actions_indicator(
1580 &self,
1581 line_height: Pixels,
1582 newest_selection_head: DisplayPoint,
1583 scroll_pixel_position: gpui::Point<Pixels>,
1584 gutter_dimensions: &GutterDimensions,
1585 gutter_hitbox: &Hitbox,
1586 cx: &mut WindowContext,
1587 ) -> Option<AnyElement> {
1588 let mut active = false;
1589 let mut button = None;
1590 let row = newest_selection_head.row();
1591 self.editor.update(cx, |editor, cx| {
1592 if let Some(crate::ContextMenu::CodeActions(CodeActionsMenu {
1593 deployed_from_indicator,
1594 ..
1595 })) = editor.context_menu.read().as_ref()
1596 {
1597 active = deployed_from_indicator.map_or(true, |indicator_row| indicator_row == row);
1598 };
1599 button = editor.render_code_actions_indicator(&self.style, row, active, cx);
1600 });
1601
1602 let button = prepaint_gutter_button(
1603 button?,
1604 row,
1605 line_height,
1606 gutter_dimensions,
1607 scroll_pixel_position,
1608 gutter_hitbox,
1609 cx,
1610 );
1611
1612 Some(button)
1613 }
1614
1615 fn get_participant_color(
1616 participant_index: Option<ParticipantIndex>,
1617 cx: &WindowContext,
1618 ) -> PlayerColor {
1619 if let Some(index) = participant_index {
1620 cx.theme().players().color_for_participant(index.0)
1621 } else {
1622 cx.theme().players().absent()
1623 }
1624 }
1625
1626 fn calculate_relative_line_numbers(
1627 &self,
1628 snapshot: &EditorSnapshot,
1629 rows: &Range<DisplayRow>,
1630 relative_to: Option<DisplayRow>,
1631 ) -> HashMap<DisplayRow, DisplayRowDelta> {
1632 let mut relative_rows: HashMap<DisplayRow, DisplayRowDelta> = Default::default();
1633 let Some(relative_to) = relative_to else {
1634 return relative_rows;
1635 };
1636
1637 let start = rows.start.min(relative_to);
1638 let end = rows.end.max(relative_to);
1639
1640 let buffer_rows = snapshot
1641 .buffer_rows(start)
1642 .take(1 + end.minus(start) as usize)
1643 .collect::<Vec<_>>();
1644
1645 let head_idx = relative_to.minus(start);
1646 let mut delta = 1;
1647 let mut i = head_idx + 1;
1648 while i < buffer_rows.len() as u32 {
1649 if buffer_rows[i as usize].is_some() {
1650 if rows.contains(&DisplayRow(i + start.0)) {
1651 relative_rows.insert(DisplayRow(i + start.0), delta);
1652 }
1653 delta += 1;
1654 }
1655 i += 1;
1656 }
1657 delta = 1;
1658 i = head_idx.min(buffer_rows.len() as u32 - 1);
1659 while i > 0 && buffer_rows[i as usize].is_none() {
1660 i -= 1;
1661 }
1662
1663 while i > 0 {
1664 i -= 1;
1665 if buffer_rows[i as usize].is_some() {
1666 if rows.contains(&DisplayRow(i + start.0)) {
1667 relative_rows.insert(DisplayRow(i + start.0), delta);
1668 }
1669 delta += 1;
1670 }
1671 }
1672
1673 relative_rows
1674 }
1675
1676 fn layout_line_numbers(
1677 &self,
1678 rows: Range<DisplayRow>,
1679 buffer_rows: impl Iterator<Item = Option<MultiBufferRow>>,
1680 active_rows: &BTreeMap<DisplayRow, bool>,
1681 newest_selection_head: Option<DisplayPoint>,
1682 snapshot: &EditorSnapshot,
1683 cx: &mut WindowContext,
1684 ) -> Vec<Option<ShapedLine>> {
1685 let include_line_numbers = snapshot.show_line_numbers.unwrap_or_else(|| {
1686 EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode == EditorMode::Full
1687 });
1688 if !include_line_numbers {
1689 return Vec::new();
1690 }
1691
1692 let editor = self.editor.read(cx);
1693 let newest_selection_head = newest_selection_head.unwrap_or_else(|| {
1694 let newest = editor.selections.newest::<Point>(cx);
1695 SelectionLayout::new(
1696 newest,
1697 editor.selections.line_mode,
1698 editor.cursor_shape,
1699 &snapshot.display_snapshot,
1700 true,
1701 true,
1702 None,
1703 )
1704 .head
1705 });
1706 let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
1707
1708 let is_relative = EditorSettings::get_global(cx).relative_line_numbers;
1709 let relative_to = if is_relative {
1710 Some(newest_selection_head.row())
1711 } else {
1712 None
1713 };
1714 let relative_rows = self.calculate_relative_line_numbers(snapshot, &rows, relative_to);
1715 let mut line_number = String::new();
1716 buffer_rows
1717 .into_iter()
1718 .enumerate()
1719 .map(|(ix, multibuffer_row)| {
1720 let multibuffer_row = multibuffer_row?;
1721 let display_row = DisplayRow(rows.start.0 + ix as u32);
1722 let color = if active_rows.contains_key(&display_row) {
1723 cx.theme().colors().editor_active_line_number
1724 } else {
1725 cx.theme().colors().editor_line_number
1726 };
1727 line_number.clear();
1728 let default_number = multibuffer_row.0 + 1;
1729 let number = relative_rows
1730 .get(&DisplayRow(ix as u32 + rows.start.0))
1731 .unwrap_or(&default_number);
1732 write!(&mut line_number, "{number}").unwrap();
1733 let run = TextRun {
1734 len: line_number.len(),
1735 font: self.style.text.font(),
1736 color,
1737 background_color: None,
1738 underline: None,
1739 strikethrough: None,
1740 };
1741 let shaped_line = cx
1742 .text_system()
1743 .shape_line(line_number.clone().into(), font_size, &[run])
1744 .unwrap();
1745 Some(shaped_line)
1746 })
1747 .collect()
1748 }
1749
1750 fn layout_gutter_fold_toggles(
1751 &self,
1752 rows: Range<DisplayRow>,
1753 buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
1754 active_rows: &BTreeMap<DisplayRow, bool>,
1755 snapshot: &EditorSnapshot,
1756 cx: &mut WindowContext,
1757 ) -> Vec<Option<AnyElement>> {
1758 let include_fold_statuses = EditorSettings::get_global(cx).gutter.folds
1759 && snapshot.mode == EditorMode::Full
1760 && self.editor.read(cx).is_singleton(cx);
1761 if include_fold_statuses {
1762 buffer_rows
1763 .into_iter()
1764 .enumerate()
1765 .map(|(ix, row)| {
1766 if let Some(multibuffer_row) = row {
1767 let display_row = DisplayRow(rows.start.0 + ix as u32);
1768 let active = active_rows.contains_key(&display_row);
1769 snapshot.render_fold_toggle(
1770 multibuffer_row,
1771 active,
1772 self.editor.clone(),
1773 cx,
1774 )
1775 } else {
1776 None
1777 }
1778 })
1779 .collect()
1780 } else {
1781 Vec::new()
1782 }
1783 }
1784
1785 fn layout_crease_trailers(
1786 &self,
1787 buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
1788 snapshot: &EditorSnapshot,
1789 cx: &mut WindowContext,
1790 ) -> Vec<Option<AnyElement>> {
1791 buffer_rows
1792 .into_iter()
1793 .map(|row| {
1794 if let Some(multibuffer_row) = row {
1795 snapshot.render_crease_trailer(multibuffer_row, cx)
1796 } else {
1797 None
1798 }
1799 })
1800 .collect()
1801 }
1802
1803 fn layout_lines(
1804 &self,
1805 rows: Range<DisplayRow>,
1806 line_number_layouts: &[Option<ShapedLine>],
1807 snapshot: &EditorSnapshot,
1808 cx: &mut WindowContext,
1809 ) -> Vec<LineWithInvisibles> {
1810 if rows.start >= rows.end {
1811 return Vec::new();
1812 }
1813
1814 // Show the placeholder when the editor is empty
1815 if snapshot.is_empty() {
1816 let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
1817 let placeholder_color = cx.theme().colors().text_placeholder;
1818 let placeholder_text = snapshot.placeholder_text();
1819
1820 let placeholder_lines = placeholder_text
1821 .as_ref()
1822 .map_or("", AsRef::as_ref)
1823 .split('\n')
1824 .skip(rows.start.0 as usize)
1825 .chain(iter::repeat(""))
1826 .take(rows.len());
1827 placeholder_lines
1828 .filter_map(move |line| {
1829 let run = TextRun {
1830 len: line.len(),
1831 font: self.style.text.font(),
1832 color: placeholder_color,
1833 background_color: None,
1834 underline: Default::default(),
1835 strikethrough: None,
1836 };
1837 cx.text_system()
1838 .shape_line(line.to_string().into(), font_size, &[run])
1839 .log_err()
1840 })
1841 .map(|line| LineWithInvisibles {
1842 width: line.width,
1843 len: line.len,
1844 fragments: smallvec![LineFragment::Text(line)],
1845 invisibles: Vec::new(),
1846 font_size,
1847 })
1848 .collect()
1849 } else {
1850 let chunks = snapshot.highlighted_chunks(rows.clone(), true, &self.style);
1851 LineWithInvisibles::from_chunks(
1852 chunks,
1853 &self.style.text,
1854 MAX_LINE_LEN,
1855 rows.len(),
1856 line_number_layouts,
1857 snapshot.mode,
1858 cx,
1859 )
1860 }
1861 }
1862
1863 fn prepaint_lines(
1864 &self,
1865 start_row: DisplayRow,
1866 line_layouts: &mut [LineWithInvisibles],
1867 line_height: Pixels,
1868 scroll_pixel_position: gpui::Point<Pixels>,
1869 content_origin: gpui::Point<Pixels>,
1870 cx: &mut WindowContext,
1871 ) -> SmallVec<[AnyElement; 1]> {
1872 let mut line_elements = SmallVec::new();
1873 for (ix, line) in line_layouts.iter_mut().enumerate() {
1874 let row = start_row + DisplayRow(ix as u32);
1875 line.prepaint(
1876 line_height,
1877 scroll_pixel_position,
1878 row,
1879 content_origin,
1880 &mut line_elements,
1881 cx,
1882 );
1883 }
1884 line_elements
1885 }
1886
1887 #[allow(clippy::too_many_arguments)]
1888 fn build_blocks(
1889 &self,
1890 rows: Range<DisplayRow>,
1891 snapshot: &EditorSnapshot,
1892 hitbox: &Hitbox,
1893 text_hitbox: &Hitbox,
1894 scroll_width: &mut Pixels,
1895 gutter_dimensions: &GutterDimensions,
1896 em_width: Pixels,
1897 text_x: Pixels,
1898 line_height: Pixels,
1899 line_layouts: &[LineWithInvisibles],
1900 cx: &mut WindowContext,
1901 ) -> Vec<BlockLayout> {
1902 let mut block_id = 0;
1903 let (fixed_blocks, non_fixed_blocks) = snapshot
1904 .blocks_in_range(rows.clone())
1905 .partition::<Vec<_>, _>(|(_, block)| match block {
1906 TransformBlock::ExcerptHeader { .. } => false,
1907 TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
1908 TransformBlock::ExcerptFooter { .. } => false,
1909 });
1910
1911 let render_block = |block: &TransformBlock,
1912 available_space: Size<AvailableSpace>,
1913 block_id: usize,
1914 block_row_start: DisplayRow,
1915 cx: &mut WindowContext| {
1916 let mut element = match block {
1917 TransformBlock::Custom(block) => {
1918 let align_to = block
1919 .position()
1920 .to_point(&snapshot.buffer_snapshot)
1921 .to_display_point(snapshot);
1922 let anchor_x = text_x
1923 + if rows.contains(&align_to.row()) {
1924 line_layouts[align_to.row().minus(rows.start) as usize]
1925 .x_for_index(align_to.column() as usize)
1926 } else {
1927 layout_line(align_to.row(), snapshot, &self.style, cx)
1928 .x_for_index(align_to.column() as usize)
1929 };
1930
1931 block.render(&mut BlockContext {
1932 context: cx,
1933 anchor_x,
1934 gutter_dimensions,
1935 line_height,
1936 em_width,
1937 block_id,
1938 max_width: text_hitbox.size.width.max(*scroll_width),
1939 editor_style: &self.style,
1940 })
1941 }
1942
1943 TransformBlock::ExcerptHeader {
1944 buffer,
1945 range,
1946 starts_new_buffer,
1947 height,
1948 id,
1949 show_excerpt_controls,
1950 ..
1951 } => {
1952 let include_root = self
1953 .editor
1954 .read(cx)
1955 .project
1956 .as_ref()
1957 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
1958 .unwrap_or_default();
1959
1960 #[derive(Clone)]
1961 struct JumpData {
1962 position: Point,
1963 anchor: text::Anchor,
1964 path: ProjectPath,
1965 line_offset_from_top: u32,
1966 }
1967
1968 let jump_data = project::File::from_dyn(buffer.file()).map(|file| {
1969 let jump_path = ProjectPath {
1970 worktree_id: file.worktree_id(cx),
1971 path: file.path.clone(),
1972 };
1973 let jump_anchor = range
1974 .primary
1975 .as_ref()
1976 .map_or(range.context.start, |primary| primary.start);
1977
1978 let excerpt_start = range.context.start;
1979 let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
1980 let offset_from_excerpt_start = if jump_anchor == excerpt_start {
1981 0
1982 } else {
1983 let excerpt_start_row =
1984 language::ToPoint::to_point(&jump_anchor, buffer).row;
1985 jump_position.row - excerpt_start_row
1986 };
1987
1988 let line_offset_from_top =
1989 block_row_start.0 + *height as u32 + offset_from_excerpt_start
1990 - snapshot
1991 .scroll_anchor
1992 .scroll_position(&snapshot.display_snapshot)
1993 .y as u32;
1994
1995 JumpData {
1996 position: jump_position,
1997 anchor: jump_anchor,
1998 path: jump_path,
1999 line_offset_from_top,
2000 }
2001 });
2002
2003 let icon_offset = gutter_dimensions.width
2004 - (gutter_dimensions.left_padding + gutter_dimensions.margin);
2005
2006 let element = if *starts_new_buffer {
2007 let path = buffer.resolve_file_path(cx, include_root);
2008 let mut filename = None;
2009 let mut parent_path = None;
2010 // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
2011 if let Some(path) = path {
2012 filename = path.file_name().map(|f| f.to_string_lossy().to_string());
2013 parent_path = path
2014 .parent()
2015 .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
2016 }
2017
2018 let header_padding = px(6.0);
2019
2020 v_flex()
2021 .id(("path excerpt header", block_id))
2022 .size_full()
2023 .p(header_padding)
2024 .child(
2025 h_flex()
2026 .flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
2027 .id("path header block")
2028 .pl(gpui::px(12.))
2029 .pr(gpui::px(8.))
2030 .rounded_md()
2031 .shadow_md()
2032 .border_1()
2033 .border_color(cx.theme().colors().border)
2034 .bg(cx.theme().colors().editor_subheader_background)
2035 .justify_between()
2036 .hover(|style| style.bg(cx.theme().colors().element_hover))
2037 .child(
2038 h_flex().gap_3().child(
2039 h_flex()
2040 .gap_2()
2041 .child(
2042 filename
2043 .map(SharedString::from)
2044 .unwrap_or_else(|| "untitled".into()),
2045 )
2046 .when_some(parent_path, |then, path| {
2047 then.child(
2048 div().child(path).text_color(
2049 cx.theme().colors().text_muted,
2050 ),
2051 )
2052 }),
2053 ),
2054 )
2055 .when_some(jump_data.clone(), |this, jump_data| {
2056 this.cursor_pointer()
2057 .tooltip(|cx| {
2058 Tooltip::for_action(
2059 "Jump to File",
2060 &OpenExcerpts,
2061 cx,
2062 )
2063 })
2064 .on_mouse_down(MouseButton::Left, |_, cx| {
2065 cx.stop_propagation()
2066 })
2067 .on_click(cx.listener_for(&self.editor, {
2068 move |editor, _, cx| {
2069 editor.jump(
2070 jump_data.path.clone(),
2071 jump_data.position,
2072 jump_data.anchor,
2073 jump_data.line_offset_from_top,
2074 cx,
2075 );
2076 }
2077 }))
2078 }),
2079 )
2080 .children(show_excerpt_controls.then(|| {
2081 h_flex()
2082 .flex_basis(Length::Definite(DefiniteLength::Fraction(0.333)))
2083 .pt_1()
2084 .justify_end()
2085 .flex_none()
2086 .w(icon_offset - header_padding)
2087 .child(
2088 ButtonLike::new("expand-icon")
2089 .style(ButtonStyle::Transparent)
2090 .child(
2091 svg()
2092 .path(IconName::ArrowUpFromLine.path())
2093 .size(IconSize::XSmall.rems())
2094 .text_color(
2095 cx.theme().colors().editor_line_number,
2096 )
2097 .group("")
2098 .hover(|style| {
2099 style.text_color(
2100 cx.theme()
2101 .colors()
2102 .editor_active_line_number,
2103 )
2104 }),
2105 )
2106 .on_click(cx.listener_for(&self.editor, {
2107 let id = *id;
2108 move |editor, _, cx| {
2109 editor.expand_excerpt(
2110 id,
2111 multi_buffer::ExpandExcerptDirection::Up,
2112 cx,
2113 );
2114 }
2115 }))
2116 .tooltip({
2117 move |cx| {
2118 Tooltip::for_action(
2119 "Expand Excerpt",
2120 &ExpandExcerpts { lines: 0 },
2121 cx,
2122 )
2123 }
2124 }),
2125 )
2126 }))
2127 } else {
2128 v_flex()
2129 .id(("excerpt header", block_id))
2130 .size_full()
2131 .child(
2132 div()
2133 .flex()
2134 .v_flex()
2135 .justify_start()
2136 .id("jump to collapsed context")
2137 .w(relative(1.0))
2138 .h_full()
2139 .child(
2140 div()
2141 .h_px()
2142 .w_full()
2143 .bg(cx.theme().colors().border_variant)
2144 .group_hover("excerpt-jump-action", |style| {
2145 style.bg(cx.theme().colors().border)
2146 }),
2147 ),
2148 )
2149 .child(
2150 h_flex()
2151 .justify_end()
2152 .flex_none()
2153 .w(icon_offset)
2154 .h_full()
2155 .child(
2156 show_excerpt_controls.then(|| {
2157 ButtonLike::new("expand-icon")
2158 .style(ButtonStyle::Transparent)
2159 .child(
2160 svg()
2161 .path(IconName::ArrowUpFromLine.path())
2162 .size(IconSize::XSmall.rems())
2163 .text_color(
2164 cx.theme().colors().editor_line_number,
2165 )
2166 .group("")
2167 .hover(|style| {
2168 style.text_color(
2169 cx.theme()
2170 .colors()
2171 .editor_active_line_number,
2172 )
2173 }),
2174 )
2175 .on_click(cx.listener_for(&self.editor, {
2176 let id = *id;
2177 move |editor, _, cx| {
2178 editor.expand_excerpt(
2179 id,
2180 multi_buffer::ExpandExcerptDirection::Up,
2181 cx,
2182 );
2183 }
2184 }))
2185 .tooltip({
2186 move |cx| {
2187 Tooltip::for_action(
2188 "Expand Excerpt",
2189 &ExpandExcerpts { lines: 0 },
2190 cx,
2191 )
2192 }
2193 })
2194 }).unwrap_or_else(|| {
2195 ButtonLike::new("jump-icon")
2196 .style(ButtonStyle::Transparent)
2197 .child(
2198 svg()
2199 .path(IconName::ArrowUpRight.path())
2200 .size(IconSize::XSmall.rems())
2201 .text_color(
2202 cx.theme().colors().border_variant,
2203 )
2204 .group("excerpt-jump-action")
2205 .group_hover("excerpt-jump-action", |style| {
2206 style.text_color(
2207 cx.theme().colors().border
2208
2209 )
2210 })
2211 )
2212 .when_some(jump_data.clone(), |this, jump_data| {
2213 this.on_click(cx.listener_for(&self.editor, {
2214 let path = jump_data.path.clone();
2215 move |editor, _, cx| {
2216 cx.stop_propagation();
2217
2218 editor.jump(
2219 path.clone(),
2220 jump_data.position,
2221 jump_data.anchor,
2222 jump_data.line_offset_from_top,
2223 cx,
2224 );
2225 }
2226 }))
2227 .tooltip(move |cx| {
2228 Tooltip::for_action(
2229 format!(
2230 "Jump to {}:L{}",
2231 jump_data.path.path.display(),
2232 jump_data.position.row + 1
2233 ),
2234 &OpenExcerpts,
2235 cx,
2236 )
2237 })
2238 })
2239 })
2240
2241 ),
2242 )
2243 .group("excerpt-jump-action")
2244 .cursor_pointer()
2245 .when_some(jump_data.clone(), |this, jump_data| {
2246 this.on_click(cx.listener_for(&self.editor, {
2247 let path = jump_data.path.clone();
2248 move |editor, _, cx| {
2249 cx.stop_propagation();
2250
2251 editor.jump(
2252 path.clone(),
2253 jump_data.position,
2254 jump_data.anchor,
2255 jump_data.line_offset_from_top,
2256 cx,
2257 );
2258 }
2259 }))
2260 .tooltip(move |cx| {
2261 Tooltip::for_action(
2262 format!(
2263 "Jump to {}:L{}",
2264 jump_data.path.path.display(),
2265 jump_data.position.row + 1
2266 ),
2267 &OpenExcerpts,
2268 cx,
2269 )
2270 })
2271 })
2272 };
2273 element.into_any()
2274 }
2275
2276 TransformBlock::ExcerptFooter { id, .. } => {
2277 let element = v_flex().id(("excerpt footer", block_id)).size_full().child(
2278 h_flex()
2279 .justify_end()
2280 .flex_none()
2281 .w(gutter_dimensions.width
2282 - (gutter_dimensions.left_padding + gutter_dimensions.margin))
2283 .h_full()
2284 .child(
2285 ButtonLike::new("expand-icon")
2286 .style(ButtonStyle::Transparent)
2287 .child(
2288 svg()
2289 .path(IconName::ArrowDownFromLine.path())
2290 .size(IconSize::XSmall.rems())
2291 .text_color(cx.theme().colors().editor_line_number)
2292 .group("")
2293 .hover(|style| {
2294 style.text_color(
2295 cx.theme().colors().editor_active_line_number,
2296 )
2297 }),
2298 )
2299 .on_click(cx.listener_for(&self.editor, {
2300 let id = *id;
2301 move |editor, _, cx| {
2302 editor.expand_excerpt(
2303 id,
2304 multi_buffer::ExpandExcerptDirection::Down,
2305 cx,
2306 );
2307 }
2308 }))
2309 .tooltip({
2310 move |cx| {
2311 Tooltip::for_action(
2312 "Expand Excerpt",
2313 &ExpandExcerpts { lines: 0 },
2314 cx,
2315 )
2316 }
2317 }),
2318 ),
2319 );
2320 element.into_any()
2321 }
2322 };
2323
2324 let size = element.layout_as_root(available_space, cx);
2325 (element, size)
2326 };
2327
2328 let mut fixed_block_max_width = Pixels::ZERO;
2329 let mut blocks = Vec::new();
2330 for (row, block) in fixed_blocks {
2331 let available_space = size(
2332 AvailableSpace::MinContent,
2333 AvailableSpace::Definite(block.height() as f32 * line_height),
2334 );
2335 let (element, element_size) = render_block(block, available_space, block_id, row, cx);
2336 block_id += 1;
2337 fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
2338 blocks.push(BlockLayout {
2339 row,
2340 element,
2341 available_space,
2342 style: BlockStyle::Fixed,
2343 });
2344 }
2345 for (row, block) in non_fixed_blocks {
2346 let style = match block {
2347 TransformBlock::Custom(block) => block.style(),
2348 TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
2349 TransformBlock::ExcerptFooter { .. } => BlockStyle::Sticky,
2350 };
2351 let width = match style {
2352 BlockStyle::Sticky => hitbox.size.width,
2353 BlockStyle::Flex => hitbox
2354 .size
2355 .width
2356 .max(fixed_block_max_width)
2357 .max(gutter_dimensions.width + *scroll_width),
2358 BlockStyle::Fixed => unreachable!(),
2359 };
2360 let available_space = size(
2361 AvailableSpace::Definite(width),
2362 AvailableSpace::Definite(block.height() as f32 * line_height),
2363 );
2364 let (element, _) = render_block(block, available_space, block_id, row, cx);
2365 block_id += 1;
2366 blocks.push(BlockLayout {
2367 row,
2368 element,
2369 available_space,
2370 style,
2371 });
2372 }
2373
2374 *scroll_width = (*scroll_width).max(fixed_block_max_width - gutter_dimensions.width);
2375 blocks
2376 }
2377
2378 fn layout_blocks(
2379 &self,
2380 blocks: &mut Vec<BlockLayout>,
2381 hitbox: &Hitbox,
2382 line_height: Pixels,
2383 scroll_pixel_position: gpui::Point<Pixels>,
2384 cx: &mut WindowContext,
2385 ) {
2386 for block in blocks {
2387 let mut origin = hitbox.origin
2388 + point(
2389 Pixels::ZERO,
2390 block.row.as_f32() * line_height - scroll_pixel_position.y,
2391 );
2392 if !matches!(block.style, BlockStyle::Sticky) {
2393 origin += point(-scroll_pixel_position.x, Pixels::ZERO);
2394 }
2395 block
2396 .element
2397 .prepaint_as_root(origin, block.available_space, cx);
2398 }
2399 }
2400
2401 #[allow(clippy::too_many_arguments)]
2402 fn layout_context_menu(
2403 &self,
2404 line_height: Pixels,
2405 hitbox: &Hitbox,
2406 text_hitbox: &Hitbox,
2407 content_origin: gpui::Point<Pixels>,
2408 start_row: DisplayRow,
2409 scroll_pixel_position: gpui::Point<Pixels>,
2410 line_layouts: &[LineWithInvisibles],
2411 newest_selection_head: DisplayPoint,
2412 gutter_overshoot: Pixels,
2413 cx: &mut WindowContext,
2414 ) -> bool {
2415 let max_height = cmp::min(
2416 12. * line_height,
2417 cmp::max(3. * line_height, (hitbox.size.height - line_height) / 2.),
2418 );
2419 let Some((position, mut context_menu)) = self.editor.update(cx, |editor, cx| {
2420 if editor.context_menu_visible() {
2421 editor.render_context_menu(newest_selection_head, &self.style, max_height, cx)
2422 } else {
2423 None
2424 }
2425 }) else {
2426 return false;
2427 };
2428
2429 let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
2430 let context_menu_size = context_menu.layout_as_root(available_space, cx);
2431
2432 let (x, y) = match position {
2433 crate::ContextMenuOrigin::EditorPoint(point) => {
2434 let cursor_row_layout = &line_layouts[point.row().minus(start_row) as usize];
2435 let x = cursor_row_layout.x_for_index(point.column() as usize)
2436 - scroll_pixel_position.x;
2437 let y = point.row().next_row().as_f32() * line_height - scroll_pixel_position.y;
2438 (x, y)
2439 }
2440 crate::ContextMenuOrigin::GutterIndicator(row) => {
2441 // Context menu was spawned via a click on a gutter. Ensure it's a bit closer to the indicator than just a plain first column of the
2442 // text field.
2443 let x = -gutter_overshoot;
2444 let y = row.next_row().as_f32() * line_height - scroll_pixel_position.y;
2445 (x, y)
2446 }
2447 };
2448
2449 let mut list_origin = content_origin + point(x, y);
2450 let list_width = context_menu_size.width;
2451 let list_height = context_menu_size.height;
2452
2453 // Snap the right edge of the list to the right edge of the window if
2454 // its horizontal bounds overflow.
2455 if list_origin.x + list_width > cx.viewport_size().width {
2456 list_origin.x = (cx.viewport_size().width - list_width).max(Pixels::ZERO);
2457 }
2458
2459 if list_origin.y + list_height > text_hitbox.lower_right().y {
2460 list_origin.y -= line_height + list_height;
2461 }
2462
2463 cx.defer_draw(context_menu, list_origin, 1);
2464 true
2465 }
2466
2467 fn layout_mouse_context_menu(&self, cx: &mut WindowContext) -> Option<AnyElement> {
2468 let mouse_context_menu = self.editor.read(cx).mouse_context_menu.as_ref()?;
2469 let mut element = deferred(
2470 anchored()
2471 .position(mouse_context_menu.position)
2472 .child(mouse_context_menu.context_menu.clone())
2473 .anchor(AnchorCorner::TopLeft)
2474 .snap_to_window(),
2475 )
2476 .with_priority(1)
2477 .into_any();
2478
2479 element.prepaint_as_root(gpui::Point::default(), AvailableSpace::min_size(), cx);
2480 Some(element)
2481 }
2482
2483 #[allow(clippy::too_many_arguments)]
2484 fn layout_hover_popovers(
2485 &self,
2486 snapshot: &EditorSnapshot,
2487 hitbox: &Hitbox,
2488 text_hitbox: &Hitbox,
2489 visible_display_row_range: Range<DisplayRow>,
2490 content_origin: gpui::Point<Pixels>,
2491 scroll_pixel_position: gpui::Point<Pixels>,
2492 line_layouts: &[LineWithInvisibles],
2493 line_height: Pixels,
2494 em_width: Pixels,
2495 cx: &mut WindowContext,
2496 ) {
2497 struct MeasuredHoverPopover {
2498 element: AnyElement,
2499 size: Size<Pixels>,
2500 horizontal_offset: Pixels,
2501 }
2502
2503 let max_size = size(
2504 (120. * em_width) // Default size
2505 .min(hitbox.size.width / 2.) // Shrink to half of the editor width
2506 .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
2507 (16. * line_height) // Default size
2508 .min(hitbox.size.height / 2.) // Shrink to half of the editor height
2509 .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
2510 );
2511
2512 let hover_popovers = self.editor.update(cx, |editor, cx| {
2513 editor.hover_state.render(
2514 &snapshot,
2515 &self.style,
2516 visible_display_row_range.clone(),
2517 max_size,
2518 editor.workspace.as_ref().map(|(w, _)| w.clone()),
2519 cx,
2520 )
2521 });
2522 let Some((position, hover_popovers)) = hover_popovers else {
2523 return;
2524 };
2525
2526 let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
2527
2528 // This is safe because we check on layout whether the required row is available
2529 let hovered_row_layout =
2530 &line_layouts[position.row().minus(visible_display_row_range.start) as usize];
2531
2532 // Compute Hovered Point
2533 let x =
2534 hovered_row_layout.x_for_index(position.column() as usize) - scroll_pixel_position.x;
2535 let y = position.row().as_f32() * line_height - scroll_pixel_position.y;
2536 let hovered_point = content_origin + point(x, y);
2537
2538 let mut overall_height = Pixels::ZERO;
2539 let mut measured_hover_popovers = Vec::new();
2540 for mut hover_popover in hover_popovers {
2541 let size = hover_popover.layout_as_root(available_space, cx);
2542 let horizontal_offset =
2543 (text_hitbox.upper_right().x - (hovered_point.x + size.width)).min(Pixels::ZERO);
2544
2545 overall_height += HOVER_POPOVER_GAP + size.height;
2546
2547 measured_hover_popovers.push(MeasuredHoverPopover {
2548 element: hover_popover,
2549 size,
2550 horizontal_offset,
2551 });
2552 }
2553 overall_height += HOVER_POPOVER_GAP;
2554
2555 fn draw_occluder(width: Pixels, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
2556 let mut occlusion = div()
2557 .size_full()
2558 .occlude()
2559 .on_mouse_move(|_, cx| cx.stop_propagation())
2560 .into_any_element();
2561 occlusion.layout_as_root(size(width, HOVER_POPOVER_GAP).into(), cx);
2562 cx.defer_draw(occlusion, origin, 2);
2563 }
2564
2565 if hovered_point.y > overall_height {
2566 // There is enough space above. Render popovers above the hovered point
2567 let mut current_y = hovered_point.y;
2568 for (position, popover) in measured_hover_popovers.into_iter().with_position() {
2569 let size = popover.size;
2570 let popover_origin = point(
2571 hovered_point.x + popover.horizontal_offset,
2572 current_y - size.height,
2573 );
2574
2575 cx.defer_draw(popover.element, popover_origin, 2);
2576 if position != itertools::Position::Last {
2577 let origin = point(popover_origin.x, popover_origin.y - HOVER_POPOVER_GAP);
2578 draw_occluder(size.width, origin, cx);
2579 }
2580
2581 current_y = popover_origin.y - HOVER_POPOVER_GAP;
2582 }
2583 } else {
2584 // There is not enough space above. Render popovers below the hovered point
2585 let mut current_y = hovered_point.y + line_height;
2586 for (position, popover) in measured_hover_popovers.into_iter().with_position() {
2587 let size = popover.size;
2588 let popover_origin = point(hovered_point.x + popover.horizontal_offset, current_y);
2589
2590 cx.defer_draw(popover.element, popover_origin, 2);
2591 if position != itertools::Position::Last {
2592 let origin = point(popover_origin.x, popover_origin.y + size.height);
2593 draw_occluder(size.width, origin, cx);
2594 }
2595
2596 current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
2597 }
2598 }
2599 }
2600
2601 fn paint_background(&self, layout: &EditorLayout, cx: &mut WindowContext) {
2602 cx.paint_layer(layout.hitbox.bounds, |cx| {
2603 let scroll_top = layout.position_map.snapshot.scroll_position().y;
2604 let gutter_bg = cx.theme().colors().editor_gutter_background;
2605 cx.paint_quad(fill(layout.gutter_hitbox.bounds, gutter_bg));
2606 cx.paint_quad(fill(layout.text_hitbox.bounds, self.style.background));
2607
2608 if let EditorMode::Full = layout.mode {
2609 let mut active_rows = layout.active_rows.iter().peekable();
2610 while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
2611 let mut end_row = start_row.0;
2612 while active_rows
2613 .peek()
2614 .map_or(false, |(active_row, has_selection)| {
2615 active_row.0 == end_row + 1
2616 && *has_selection == contains_non_empty_selection
2617 })
2618 {
2619 active_rows.next().unwrap();
2620 end_row += 1;
2621 }
2622
2623 if !contains_non_empty_selection {
2624 let highlight_h_range =
2625 match layout.position_map.snapshot.current_line_highlight {
2626 CurrentLineHighlight::Gutter => Some(Range {
2627 start: layout.hitbox.left(),
2628 end: layout.gutter_hitbox.right(),
2629 }),
2630 CurrentLineHighlight::Line => Some(Range {
2631 start: layout.text_hitbox.bounds.left(),
2632 end: layout.text_hitbox.bounds.right(),
2633 }),
2634 CurrentLineHighlight::All => Some(Range {
2635 start: layout.hitbox.left(),
2636 end: layout.hitbox.right(),
2637 }),
2638 CurrentLineHighlight::None => None,
2639 };
2640 if let Some(range) = highlight_h_range {
2641 let active_line_bg = cx.theme().colors().editor_active_line_background;
2642 let bounds = Bounds {
2643 origin: point(
2644 range.start,
2645 layout.hitbox.origin.y
2646 + (start_row.as_f32() - scroll_top)
2647 * layout.position_map.line_height,
2648 ),
2649 size: size(
2650 range.end - range.start,
2651 layout.position_map.line_height
2652 * (end_row - start_row.0 + 1) as f32,
2653 ),
2654 };
2655 cx.paint_quad(fill(bounds, active_line_bg));
2656 }
2657 }
2658 }
2659
2660 let mut paint_highlight =
2661 |highlight_row_start: DisplayRow, highlight_row_end: DisplayRow, color| {
2662 let origin = point(
2663 layout.hitbox.origin.x,
2664 layout.hitbox.origin.y
2665 + (highlight_row_start.as_f32() - scroll_top)
2666 * layout.position_map.line_height,
2667 );
2668 let size = size(
2669 layout.hitbox.size.width,
2670 layout.position_map.line_height
2671 * highlight_row_end.next_row().minus(highlight_row_start) as f32,
2672 );
2673 cx.paint_quad(fill(Bounds { origin, size }, color));
2674 };
2675
2676 let mut current_paint: Option<(Hsla, Range<DisplayRow>)> = None;
2677 for (&new_row, &new_color) in &layout.highlighted_rows {
2678 match &mut current_paint {
2679 Some((current_color, current_range)) => {
2680 let current_color = *current_color;
2681 let new_range_started = current_color != new_color
2682 || current_range.end.next_row() != new_row;
2683 if new_range_started {
2684 paint_highlight(
2685 current_range.start,
2686 current_range.end,
2687 current_color,
2688 );
2689 current_paint = Some((new_color, new_row..new_row));
2690 continue;
2691 } else {
2692 current_range.end = current_range.end.next_row();
2693 }
2694 }
2695 None => current_paint = Some((new_color, new_row..new_row)),
2696 };
2697 }
2698 if let Some((color, range)) = current_paint {
2699 paint_highlight(range.start, range.end, color);
2700 }
2701
2702 let scroll_left =
2703 layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
2704
2705 for (wrap_position, active) in layout.wrap_guides.iter() {
2706 let x = (layout.text_hitbox.origin.x
2707 + *wrap_position
2708 + layout.position_map.em_width / 2.)
2709 - scroll_left;
2710
2711 let show_scrollbars = layout
2712 .scrollbar_layout
2713 .as_ref()
2714 .map_or(false, |scrollbar| scrollbar.visible);
2715 if x < layout.text_hitbox.origin.x
2716 || (show_scrollbars && x > self.scrollbar_left(&layout.hitbox.bounds))
2717 {
2718 continue;
2719 }
2720
2721 let color = if *active {
2722 cx.theme().colors().editor_active_wrap_guide
2723 } else {
2724 cx.theme().colors().editor_wrap_guide
2725 };
2726 cx.paint_quad(fill(
2727 Bounds {
2728 origin: point(x, layout.text_hitbox.origin.y),
2729 size: size(px(1.), layout.text_hitbox.size.height),
2730 },
2731 color,
2732 ));
2733 }
2734 }
2735 })
2736 }
2737
2738 fn paint_indent_guides(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
2739 let Some(indent_guides) = &layout.indent_guides else {
2740 return;
2741 };
2742
2743 let faded_color = |color: Hsla, alpha: f32| {
2744 let mut faded = color;
2745 faded.a = alpha;
2746 faded
2747 };
2748
2749 for indent_guide in indent_guides {
2750 let indent_accent_colors = cx.theme().accents().color_for_index(indent_guide.depth);
2751 let settings = indent_guide.settings;
2752
2753 // TODO fixed for now, expose them through themes later
2754 const INDENT_AWARE_ALPHA: f32 = 0.2;
2755 const INDENT_AWARE_ACTIVE_ALPHA: f32 = 0.4;
2756 const INDENT_AWARE_BACKGROUND_ALPHA: f32 = 0.1;
2757 const INDENT_AWARE_BACKGROUND_ACTIVE_ALPHA: f32 = 0.2;
2758
2759 let line_color = match (settings.coloring, indent_guide.active) {
2760 (IndentGuideColoring::Disabled, _) => None,
2761 (IndentGuideColoring::Fixed, false) => {
2762 Some(cx.theme().colors().editor_indent_guide)
2763 }
2764 (IndentGuideColoring::Fixed, true) => {
2765 Some(cx.theme().colors().editor_indent_guide_active)
2766 }
2767 (IndentGuideColoring::IndentAware, false) => {
2768 Some(faded_color(indent_accent_colors, INDENT_AWARE_ALPHA))
2769 }
2770 (IndentGuideColoring::IndentAware, true) => {
2771 Some(faded_color(indent_accent_colors, INDENT_AWARE_ACTIVE_ALPHA))
2772 }
2773 };
2774
2775 let background_color = match (settings.background_coloring, indent_guide.active) {
2776 (IndentGuideBackgroundColoring::Disabled, _) => None,
2777 (IndentGuideBackgroundColoring::IndentAware, false) => Some(faded_color(
2778 indent_accent_colors,
2779 INDENT_AWARE_BACKGROUND_ALPHA,
2780 )),
2781 (IndentGuideBackgroundColoring::IndentAware, true) => Some(faded_color(
2782 indent_accent_colors,
2783 INDENT_AWARE_BACKGROUND_ACTIVE_ALPHA,
2784 )),
2785 };
2786
2787 let requested_line_width = settings.line_width.clamp(1, 10);
2788 let mut line_indicator_width = 0.;
2789 if let Some(color) = line_color {
2790 cx.paint_quad(fill(
2791 Bounds {
2792 origin: indent_guide.origin,
2793 size: size(px(requested_line_width as f32), indent_guide.length),
2794 },
2795 color,
2796 ));
2797 line_indicator_width = requested_line_width as f32;
2798 }
2799
2800 if let Some(color) = background_color {
2801 let width = indent_guide.single_indent_width - px(line_indicator_width);
2802 cx.paint_quad(fill(
2803 Bounds {
2804 origin: point(
2805 indent_guide.origin.x + px(line_indicator_width),
2806 indent_guide.origin.y,
2807 ),
2808 size: size(width, indent_guide.length),
2809 },
2810 color,
2811 ));
2812 }
2813 }
2814 }
2815
2816 fn paint_line_numbers(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
2817 let line_height = layout.position_map.line_height;
2818 let scroll_position = layout.position_map.snapshot.scroll_position();
2819 let scroll_top = scroll_position.y * line_height;
2820
2821 cx.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox);
2822
2823 for (ix, line) in layout.line_numbers.iter().enumerate() {
2824 if let Some(line) = line {
2825 let line_origin = layout.gutter_hitbox.origin
2826 + point(
2827 layout.gutter_hitbox.size.width
2828 - line.width
2829 - layout.gutter_dimensions.right_padding,
2830 ix as f32 * line_height - (scroll_top % line_height),
2831 );
2832
2833 line.paint(line_origin, line_height, cx).log_err();
2834 }
2835 }
2836 }
2837
2838 fn paint_diff_hunks(layout: &EditorLayout, cx: &mut WindowContext) {
2839 if layout.display_hunks.is_empty() {
2840 return;
2841 }
2842
2843 let line_height = layout.position_map.line_height;
2844 cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
2845 for (hunk, hitbox) in &layout.display_hunks {
2846 let hunk_to_paint = match hunk {
2847 DisplayDiffHunk::Folded { .. } => {
2848 let hunk_bounds = Self::diff_hunk_bounds(
2849 &layout.position_map.snapshot,
2850 line_height,
2851 layout.gutter_hitbox.bounds,
2852 &hunk,
2853 );
2854 Some((
2855 hunk_bounds,
2856 cx.theme().status().modified,
2857 Corners::all(1. * line_height),
2858 ))
2859 }
2860 DisplayDiffHunk::Unfolded { status, .. } => {
2861 hitbox.as_ref().map(|hunk_hitbox| match status {
2862 DiffHunkStatus::Added => (
2863 hunk_hitbox.bounds,
2864 cx.theme().status().created,
2865 Corners::all(0.05 * line_height),
2866 ),
2867 DiffHunkStatus::Modified => (
2868 hunk_hitbox.bounds,
2869 cx.theme().status().modified,
2870 Corners::all(0.05 * line_height),
2871 ),
2872 DiffHunkStatus::Removed => (
2873 Bounds::new(
2874 point(
2875 hunk_hitbox.origin.x - hunk_hitbox.size.width,
2876 hunk_hitbox.origin.y,
2877 ),
2878 size(hunk_hitbox.size.width * px(2.), hunk_hitbox.size.height),
2879 ),
2880 cx.theme().status().deleted,
2881 Corners::all(1. * line_height),
2882 ),
2883 })
2884 }
2885 };
2886
2887 if let Some((hunk_bounds, background_color, corner_radii)) = hunk_to_paint {
2888 cx.paint_quad(quad(
2889 hunk_bounds,
2890 corner_radii,
2891 background_color,
2892 Edges::default(),
2893 transparent_black(),
2894 ));
2895 }
2896 }
2897 });
2898 }
2899
2900 fn diff_hunk_bounds(
2901 snapshot: &EditorSnapshot,
2902 line_height: Pixels,
2903 bounds: Bounds<Pixels>,
2904 hunk: &DisplayDiffHunk,
2905 ) -> Bounds<Pixels> {
2906 let scroll_position = snapshot.scroll_position();
2907 let scroll_top = scroll_position.y * line_height;
2908
2909 match hunk {
2910 DisplayDiffHunk::Folded { display_row, .. } => {
2911 let start_y = display_row.as_f32() * line_height - scroll_top;
2912 let end_y = start_y + line_height;
2913
2914 let width = 0.275 * line_height;
2915 let highlight_origin = bounds.origin + point(px(0.), start_y);
2916 let highlight_size = size(width, end_y - start_y);
2917 Bounds::new(highlight_origin, highlight_size)
2918 }
2919 DisplayDiffHunk::Unfolded {
2920 display_row_range,
2921 status,
2922 ..
2923 } => match status {
2924 DiffHunkStatus::Added | DiffHunkStatus::Modified => {
2925 let start_row = display_row_range.start;
2926 let end_row = display_row_range.end;
2927 // If we're in a multibuffer, row range span might include an
2928 // excerpt header, so if we were to draw the marker straight away,
2929 // the hunk might include the rows of that header.
2930 // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap.
2931 // Instead, we simply check whether the range we're dealing with includes
2932 // any excerpt headers and if so, we stop painting the diff hunk on the first row of that header.
2933 let end_row_in_current_excerpt = snapshot
2934 .blocks_in_range(start_row..end_row)
2935 .find_map(|(start_row, block)| {
2936 if matches!(block, TransformBlock::ExcerptHeader { .. }) {
2937 Some(start_row)
2938 } else {
2939 None
2940 }
2941 })
2942 .unwrap_or(end_row);
2943
2944 let start_y = start_row.as_f32() * line_height - scroll_top;
2945 let end_y = end_row_in_current_excerpt.as_f32() * line_height - scroll_top;
2946
2947 let width = 0.275 * line_height;
2948 let highlight_origin = bounds.origin + point(px(0.), start_y);
2949 let highlight_size = size(width, end_y - start_y);
2950 Bounds::new(highlight_origin, highlight_size)
2951 }
2952 DiffHunkStatus::Removed => {
2953 let row = display_row_range.start;
2954
2955 let offset = line_height / 2.;
2956 let start_y = row.as_f32() * line_height - offset - scroll_top;
2957 let end_y = start_y + line_height;
2958
2959 let width = 0.35 * line_height;
2960 let highlight_origin = bounds.origin + point(px(0.), start_y);
2961 let highlight_size = size(width, end_y - start_y);
2962 Bounds::new(highlight_origin, highlight_size)
2963 }
2964 },
2965 }
2966 }
2967
2968 fn paint_gutter_indicators(&self, layout: &mut EditorLayout, cx: &mut WindowContext) {
2969 cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
2970 cx.with_element_namespace("gutter_fold_toggles", |cx| {
2971 for fold_indicator in layout.gutter_fold_toggles.iter_mut().flatten() {
2972 fold_indicator.paint(cx);
2973 }
2974 });
2975
2976 for test_indicators in layout.test_indicators.iter_mut() {
2977 test_indicators.paint(cx);
2978 }
2979
2980 if let Some(indicator) = layout.code_actions_indicator.as_mut() {
2981 indicator.paint(cx);
2982 }
2983 });
2984 }
2985
2986 fn paint_gutter_highlights(&self, layout: &EditorLayout, cx: &mut WindowContext) {
2987 for (_, hunk_hitbox) in &layout.display_hunks {
2988 if let Some(hunk_hitbox) = hunk_hitbox {
2989 cx.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox);
2990 }
2991 }
2992
2993 let show_git_gutter = layout
2994 .position_map
2995 .snapshot
2996 .show_git_diff_gutter
2997 .unwrap_or_else(|| {
2998 matches!(
2999 ProjectSettings::get_global(cx).git.git_gutter,
3000 Some(GitGutterSetting::TrackedFiles)
3001 )
3002 });
3003 if show_git_gutter {
3004 Self::paint_diff_hunks(layout, cx)
3005 }
3006
3007 let highlight_width = 0.275 * layout.position_map.line_height;
3008 let highlight_corner_radii = Corners::all(0.05 * layout.position_map.line_height);
3009 cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
3010 for (range, color) in &layout.highlighted_gutter_ranges {
3011 let start_row = if range.start.row() < layout.visible_display_row_range.start {
3012 layout.visible_display_row_range.start - DisplayRow(1)
3013 } else {
3014 range.start.row()
3015 };
3016 let end_row = if range.end.row() > layout.visible_display_row_range.end {
3017 layout.visible_display_row_range.end + DisplayRow(1)
3018 } else {
3019 range.end.row()
3020 };
3021
3022 let start_y = layout.gutter_hitbox.top()
3023 + start_row.0 as f32 * layout.position_map.line_height
3024 - layout.position_map.scroll_pixel_position.y;
3025 let end_y = layout.gutter_hitbox.top()
3026 + (end_row.0 + 1) as f32 * layout.position_map.line_height
3027 - layout.position_map.scroll_pixel_position.y;
3028 let bounds = Bounds::from_corners(
3029 point(layout.gutter_hitbox.left(), start_y),
3030 point(layout.gutter_hitbox.left() + highlight_width, end_y),
3031 );
3032 cx.paint_quad(fill(bounds, *color).corner_radii(highlight_corner_radii));
3033 }
3034 });
3035 }
3036
3037 fn paint_blamed_display_rows(&self, layout: &mut EditorLayout, cx: &mut WindowContext) {
3038 let Some(blamed_display_rows) = layout.blamed_display_rows.take() else {
3039 return;
3040 };
3041
3042 cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
3043 for mut blame_element in blamed_display_rows.into_iter() {
3044 blame_element.paint(cx);
3045 }
3046 })
3047 }
3048
3049 fn paint_text(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
3050 cx.with_content_mask(
3051 Some(ContentMask {
3052 bounds: layout.text_hitbox.bounds,
3053 }),
3054 |cx| {
3055 let cursor_style = if self
3056 .editor
3057 .read(cx)
3058 .hovered_link_state
3059 .as_ref()
3060 .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
3061 {
3062 CursorStyle::PointingHand
3063 } else {
3064 CursorStyle::IBeam
3065 };
3066 cx.set_cursor_style(cursor_style, &layout.text_hitbox);
3067
3068 let invisible_display_ranges = self.paint_highlights(layout, cx);
3069 self.paint_lines(&invisible_display_ranges, layout, cx);
3070 self.paint_redactions(layout, cx);
3071 self.paint_cursors(layout, cx);
3072 self.paint_inline_blame(layout, cx);
3073 cx.with_element_namespace("crease_trailers", |cx| {
3074 for trailer in layout.crease_trailers.iter_mut().flatten() {
3075 trailer.element.paint(cx);
3076 }
3077 });
3078 },
3079 )
3080 }
3081
3082 fn paint_highlights(
3083 &mut self,
3084 layout: &mut EditorLayout,
3085 cx: &mut WindowContext,
3086 ) -> SmallVec<[Range<DisplayPoint>; 32]> {
3087 cx.paint_layer(layout.text_hitbox.bounds, |cx| {
3088 let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
3089 let line_end_overshoot = 0.15 * layout.position_map.line_height;
3090 for (range, color) in &layout.highlighted_ranges {
3091 self.paint_highlighted_range(
3092 range.clone(),
3093 *color,
3094 Pixels::ZERO,
3095 line_end_overshoot,
3096 layout,
3097 cx,
3098 );
3099 }
3100
3101 let corner_radius = 0.15 * layout.position_map.line_height;
3102
3103 for (player_color, selections) in &layout.selections {
3104 for selection in selections.into_iter() {
3105 self.paint_highlighted_range(
3106 selection.range.clone(),
3107 player_color.selection,
3108 corner_radius,
3109 corner_radius * 2.,
3110 layout,
3111 cx,
3112 );
3113
3114 if selection.is_local && !selection.range.is_empty() {
3115 invisible_display_ranges.push(selection.range.clone());
3116 }
3117 }
3118 }
3119 invisible_display_ranges
3120 })
3121 }
3122
3123 fn paint_lines(
3124 &mut self,
3125 invisible_display_ranges: &[Range<DisplayPoint>],
3126 layout: &mut EditorLayout,
3127 cx: &mut WindowContext,
3128 ) {
3129 let whitespace_setting = self
3130 .editor
3131 .read(cx)
3132 .buffer
3133 .read(cx)
3134 .settings_at(0, cx)
3135 .show_whitespaces;
3136
3137 for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
3138 let row = DisplayRow(layout.visible_display_row_range.start.0 + ix as u32);
3139 line_with_invisibles.draw(
3140 layout,
3141 row,
3142 layout.content_origin,
3143 whitespace_setting,
3144 invisible_display_ranges,
3145 cx,
3146 )
3147 }
3148
3149 for line_element in &mut layout.line_elements {
3150 line_element.paint(cx);
3151 }
3152 }
3153
3154 fn paint_redactions(&mut self, layout: &EditorLayout, cx: &mut WindowContext) {
3155 if layout.redacted_ranges.is_empty() {
3156 return;
3157 }
3158
3159 let line_end_overshoot = layout.line_end_overshoot();
3160
3161 // A softer than perfect black
3162 let redaction_color = gpui::rgb(0x0e1111);
3163
3164 cx.paint_layer(layout.text_hitbox.bounds, |cx| {
3165 for range in layout.redacted_ranges.iter() {
3166 self.paint_highlighted_range(
3167 range.clone(),
3168 redaction_color.into(),
3169 Pixels::ZERO,
3170 line_end_overshoot,
3171 layout,
3172 cx,
3173 );
3174 }
3175 });
3176 }
3177
3178 fn paint_cursors(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
3179 for cursor in &mut layout.visible_cursors {
3180 cursor.paint(layout.content_origin, cx);
3181 }
3182 }
3183
3184 fn paint_scrollbar(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
3185 let Some(scrollbar_layout) = layout.scrollbar_layout.as_ref() else {
3186 return;
3187 };
3188
3189 let thumb_bounds = scrollbar_layout.thumb_bounds();
3190 if scrollbar_layout.visible {
3191 cx.paint_layer(scrollbar_layout.hitbox.bounds, |cx| {
3192 cx.paint_quad(quad(
3193 scrollbar_layout.hitbox.bounds,
3194 Corners::default(),
3195 cx.theme().colors().scrollbar_track_background,
3196 Edges {
3197 top: Pixels::ZERO,
3198 right: Pixels::ZERO,
3199 bottom: Pixels::ZERO,
3200 left: ScrollbarLayout::BORDER_WIDTH,
3201 },
3202 cx.theme().colors().scrollbar_track_border,
3203 ));
3204
3205 let fast_markers =
3206 self.collect_fast_scrollbar_markers(layout, scrollbar_layout, cx);
3207 // Refresh slow scrollbar markers in the background. Below, we paint whatever markers have already been computed.
3208 self.refresh_slow_scrollbar_markers(layout, scrollbar_layout, cx);
3209
3210 let markers = self.editor.read(cx).scrollbar_marker_state.markers.clone();
3211 for marker in markers.iter().chain(&fast_markers) {
3212 let mut marker = marker.clone();
3213 marker.bounds.origin += scrollbar_layout.hitbox.origin;
3214 cx.paint_quad(marker);
3215 }
3216
3217 cx.paint_quad(quad(
3218 thumb_bounds,
3219 Corners::default(),
3220 cx.theme().colors().scrollbar_thumb_background,
3221 Edges {
3222 top: Pixels::ZERO,
3223 right: Pixels::ZERO,
3224 bottom: Pixels::ZERO,
3225 left: ScrollbarLayout::BORDER_WIDTH,
3226 },
3227 cx.theme().colors().scrollbar_thumb_border,
3228 ));
3229 });
3230 }
3231
3232 cx.set_cursor_style(CursorStyle::Arrow, &scrollbar_layout.hitbox);
3233
3234 let row_height = scrollbar_layout.row_height;
3235 let row_range = scrollbar_layout.visible_row_range.clone();
3236
3237 cx.on_mouse_event({
3238 let editor = self.editor.clone();
3239 let hitbox = scrollbar_layout.hitbox.clone();
3240 let mut mouse_position = cx.mouse_position();
3241 move |event: &MouseMoveEvent, phase, cx| {
3242 if phase == DispatchPhase::Capture {
3243 return;
3244 }
3245
3246 editor.update(cx, |editor, cx| {
3247 if event.pressed_button == Some(MouseButton::Left)
3248 && editor.scroll_manager.is_dragging_scrollbar()
3249 {
3250 let y = mouse_position.y;
3251 let new_y = event.position.y;
3252 if (hitbox.top()..hitbox.bottom()).contains(&y) {
3253 let mut position = editor.scroll_position(cx);
3254 position.y += (new_y - y) / row_height;
3255 if position.y < 0.0 {
3256 position.y = 0.0;
3257 }
3258 editor.set_scroll_position(position, cx);
3259 }
3260
3261 cx.stop_propagation();
3262 } else {
3263 editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
3264 if hitbox.is_hovered(cx) {
3265 editor.scroll_manager.show_scrollbar(cx);
3266 }
3267 }
3268 mouse_position = event.position;
3269 })
3270 }
3271 });
3272
3273 if self.editor.read(cx).scroll_manager.is_dragging_scrollbar() {
3274 cx.on_mouse_event({
3275 let editor = self.editor.clone();
3276 move |_: &MouseUpEvent, phase, cx| {
3277 if phase == DispatchPhase::Capture {
3278 return;
3279 }
3280
3281 editor.update(cx, |editor, cx| {
3282 editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
3283 cx.stop_propagation();
3284 });
3285 }
3286 });
3287 } else {
3288 cx.on_mouse_event({
3289 let editor = self.editor.clone();
3290 let hitbox = scrollbar_layout.hitbox.clone();
3291 move |event: &MouseDownEvent, phase, cx| {
3292 if phase == DispatchPhase::Capture || !hitbox.is_hovered(cx) {
3293 return;
3294 }
3295
3296 editor.update(cx, |editor, cx| {
3297 editor.scroll_manager.set_is_dragging_scrollbar(true, cx);
3298
3299 let y = event.position.y;
3300 if y < thumb_bounds.top() || thumb_bounds.bottom() < y {
3301 let center_row = ((y - hitbox.top()) / row_height).round() as u32;
3302 let top_row = center_row
3303 .saturating_sub((row_range.end - row_range.start) as u32 / 2);
3304 let mut position = editor.scroll_position(cx);
3305 position.y = top_row as f32;
3306 editor.set_scroll_position(position, cx);
3307 } else {
3308 editor.scroll_manager.show_scrollbar(cx);
3309 }
3310
3311 cx.stop_propagation();
3312 });
3313 }
3314 });
3315 }
3316 }
3317
3318 fn collect_fast_scrollbar_markers(
3319 &self,
3320 layout: &EditorLayout,
3321 scrollbar_layout: &ScrollbarLayout,
3322 cx: &mut WindowContext,
3323 ) -> Vec<PaintQuad> {
3324 const LIMIT: usize = 100;
3325 if !EditorSettings::get_global(cx).scrollbar.cursors || layout.cursors.len() > LIMIT {
3326 return vec![];
3327 }
3328 let cursor_ranges = layout
3329 .cursors
3330 .iter()
3331 .map(|(point, color)| ColoredRange {
3332 start: point.row(),
3333 end: point.row(),
3334 color: *color,
3335 })
3336 .collect_vec();
3337 scrollbar_layout.marker_quads_for_ranges(cursor_ranges, None)
3338 }
3339
3340 fn refresh_slow_scrollbar_markers(
3341 &self,
3342 layout: &EditorLayout,
3343 scrollbar_layout: &ScrollbarLayout,
3344 cx: &mut WindowContext,
3345 ) {
3346 self.editor.update(cx, |editor, cx| {
3347 if !editor.is_singleton(cx)
3348 || !editor
3349 .scrollbar_marker_state
3350 .should_refresh(scrollbar_layout.hitbox.size)
3351 {
3352 return;
3353 }
3354
3355 let scrollbar_layout = scrollbar_layout.clone();
3356 let background_highlights = editor.background_highlights.clone();
3357 let snapshot = layout.position_map.snapshot.clone();
3358 let theme = cx.theme().clone();
3359 let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
3360
3361 editor.scrollbar_marker_state.dirty = false;
3362 editor.scrollbar_marker_state.pending_refresh =
3363 Some(cx.spawn(|editor, mut cx| async move {
3364 let scrollbar_size = scrollbar_layout.hitbox.size;
3365 let scrollbar_markers = cx
3366 .background_executor()
3367 .spawn(async move {
3368 let max_point = snapshot.display_snapshot.buffer_snapshot.max_point();
3369 let mut marker_quads = Vec::new();
3370 if scrollbar_settings.git_diff {
3371 let marker_row_ranges = snapshot
3372 .buffer_snapshot
3373 .git_diff_hunks_in_range(
3374 MultiBufferRow::MIN..MultiBufferRow::MAX,
3375 )
3376 .map(|hunk| {
3377 let start_display_row =
3378 MultiBufferPoint::new(hunk.associated_range.start.0, 0)
3379 .to_display_point(&snapshot.display_snapshot)
3380 .row();
3381 let mut end_display_row =
3382 MultiBufferPoint::new(hunk.associated_range.end.0, 0)
3383 .to_display_point(&snapshot.display_snapshot)
3384 .row();
3385 if end_display_row != start_display_row {
3386 end_display_row.0 -= 1;
3387 }
3388 let color = match hunk_status(&hunk) {
3389 DiffHunkStatus::Added => theme.status().created,
3390 DiffHunkStatus::Modified => theme.status().modified,
3391 DiffHunkStatus::Removed => theme.status().deleted,
3392 };
3393 ColoredRange {
3394 start: start_display_row,
3395 end: end_display_row,
3396 color,
3397 }
3398 });
3399
3400 marker_quads.extend(
3401 scrollbar_layout
3402 .marker_quads_for_ranges(marker_row_ranges, Some(0)),
3403 );
3404 }
3405
3406 for (background_highlight_id, (_, background_ranges)) in
3407 background_highlights.iter()
3408 {
3409 let is_search_highlights = *background_highlight_id
3410 == TypeId::of::<BufferSearchHighlights>();
3411 let is_symbol_occurrences = *background_highlight_id
3412 == TypeId::of::<DocumentHighlightRead>()
3413 || *background_highlight_id
3414 == TypeId::of::<DocumentHighlightWrite>();
3415 if (is_search_highlights && scrollbar_settings.search_results)
3416 || (is_symbol_occurrences && scrollbar_settings.selected_symbol)
3417 {
3418 let mut color = theme.status().info;
3419 if is_symbol_occurrences {
3420 color.fade_out(0.5);
3421 }
3422 let marker_row_ranges =
3423 background_ranges.into_iter().map(|range| {
3424 let display_start = range
3425 .start
3426 .to_display_point(&snapshot.display_snapshot);
3427 let display_end = range
3428 .end
3429 .to_display_point(&snapshot.display_snapshot);
3430 ColoredRange {
3431 start: display_start.row(),
3432 end: display_end.row(),
3433 color,
3434 }
3435 });
3436 marker_quads.extend(
3437 scrollbar_layout
3438 .marker_quads_for_ranges(marker_row_ranges, Some(1)),
3439 );
3440 }
3441 }
3442
3443 if scrollbar_settings.diagnostics {
3444 let diagnostics = snapshot
3445 .buffer_snapshot
3446 .diagnostics_in_range::<_, Point>(
3447 Point::zero()..max_point,
3448 false,
3449 )
3450 // We want to sort by severity, in order to paint the most severe diagnostics last.
3451 .sorted_by_key(|diagnostic| {
3452 std::cmp::Reverse(diagnostic.diagnostic.severity)
3453 });
3454
3455 let marker_row_ranges = diagnostics.into_iter().map(|diagnostic| {
3456 let start_display = diagnostic
3457 .range
3458 .start
3459 .to_display_point(&snapshot.display_snapshot);
3460 let end_display = diagnostic
3461 .range
3462 .end
3463 .to_display_point(&snapshot.display_snapshot);
3464 let color = match diagnostic.diagnostic.severity {
3465 DiagnosticSeverity::ERROR => theme.status().error,
3466 DiagnosticSeverity::WARNING => theme.status().warning,
3467 DiagnosticSeverity::INFORMATION => theme.status().info,
3468 _ => theme.status().hint,
3469 };
3470 ColoredRange {
3471 start: start_display.row(),
3472 end: end_display.row(),
3473 color,
3474 }
3475 });
3476 marker_quads.extend(
3477 scrollbar_layout
3478 .marker_quads_for_ranges(marker_row_ranges, Some(2)),
3479 );
3480 }
3481
3482 Arc::from(marker_quads)
3483 })
3484 .await;
3485
3486 editor.update(&mut cx, |editor, cx| {
3487 editor.scrollbar_marker_state.markers = scrollbar_markers;
3488 editor.scrollbar_marker_state.scrollbar_size = scrollbar_size;
3489 editor.scrollbar_marker_state.pending_refresh = None;
3490 cx.notify();
3491 })?;
3492
3493 Ok(())
3494 }));
3495 });
3496 }
3497
3498 #[allow(clippy::too_many_arguments)]
3499 fn paint_highlighted_range(
3500 &self,
3501 range: Range<DisplayPoint>,
3502 color: Hsla,
3503 corner_radius: Pixels,
3504 line_end_overshoot: Pixels,
3505 layout: &EditorLayout,
3506 cx: &mut WindowContext,
3507 ) {
3508 let start_row = layout.visible_display_row_range.start;
3509 let end_row = layout.visible_display_row_range.end;
3510 if range.start != range.end {
3511 let row_range = if range.end.column() == 0 {
3512 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
3513 } else {
3514 cmp::max(range.start.row(), start_row)
3515 ..cmp::min(range.end.row().next_row(), end_row)
3516 };
3517
3518 let highlighted_range = HighlightedRange {
3519 color,
3520 line_height: layout.position_map.line_height,
3521 corner_radius,
3522 start_y: layout.content_origin.y
3523 + row_range.start.as_f32() * layout.position_map.line_height
3524 - layout.position_map.scroll_pixel_position.y,
3525 lines: row_range
3526 .iter_rows()
3527 .map(|row| {
3528 let line_layout =
3529 &layout.position_map.line_layouts[row.minus(start_row) as usize];
3530 HighlightedRangeLine {
3531 start_x: if row == range.start.row() {
3532 layout.content_origin.x
3533 + line_layout.x_for_index(range.start.column() as usize)
3534 - layout.position_map.scroll_pixel_position.x
3535 } else {
3536 layout.content_origin.x
3537 - layout.position_map.scroll_pixel_position.x
3538 },
3539 end_x: if row == range.end.row() {
3540 layout.content_origin.x
3541 + line_layout.x_for_index(range.end.column() as usize)
3542 - layout.position_map.scroll_pixel_position.x
3543 } else {
3544 layout.content_origin.x + line_layout.width + line_end_overshoot
3545 - layout.position_map.scroll_pixel_position.x
3546 },
3547 }
3548 })
3549 .collect(),
3550 };
3551
3552 highlighted_range.paint(layout.text_hitbox.bounds, cx);
3553 }
3554 }
3555
3556 fn paint_inline_blame(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
3557 if let Some(mut inline_blame) = layout.inline_blame.take() {
3558 cx.paint_layer(layout.text_hitbox.bounds, |cx| {
3559 inline_blame.paint(cx);
3560 })
3561 }
3562 }
3563
3564 fn paint_blocks(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
3565 for mut block in layout.blocks.drain(..) {
3566 block.element.paint(cx);
3567 }
3568 }
3569
3570 fn paint_mouse_context_menu(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
3571 if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() {
3572 mouse_context_menu.paint(cx);
3573 }
3574 }
3575
3576 fn paint_scroll_wheel_listener(&mut self, layout: &EditorLayout, cx: &mut WindowContext) {
3577 cx.on_mouse_event({
3578 let position_map = layout.position_map.clone();
3579 let editor = self.editor.clone();
3580 let hitbox = layout.hitbox.clone();
3581 let mut delta = ScrollDelta::default();
3582
3583 // Set a minimum scroll_sensitivity of 0.01 to make sure the user doesn't
3584 // accidentally turn off their scrolling.
3585 let scroll_sensitivity = EditorSettings::get_global(cx).scroll_sensitivity.max(0.01);
3586
3587 move |event: &ScrollWheelEvent, phase, cx| {
3588 if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
3589 delta = delta.coalesce(event.delta);
3590 editor.update(cx, |editor, cx| {
3591 let position_map: &PositionMap = &position_map;
3592
3593 let line_height = position_map.line_height;
3594 let max_glyph_width = position_map.em_width;
3595 let (delta, axis) = match delta {
3596 gpui::ScrollDelta::Pixels(mut pixels) => {
3597 //Trackpad
3598 let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
3599 (pixels, axis)
3600 }
3601
3602 gpui::ScrollDelta::Lines(lines) => {
3603 //Not trackpad
3604 let pixels =
3605 point(lines.x * max_glyph_width, lines.y * line_height);
3606 (pixels, None)
3607 }
3608 };
3609
3610 let current_scroll_position = position_map.snapshot.scroll_position();
3611 let x = (current_scroll_position.x * max_glyph_width
3612 - (delta.x * scroll_sensitivity))
3613 / max_glyph_width;
3614 let y = (current_scroll_position.y * line_height
3615 - (delta.y * scroll_sensitivity))
3616 / line_height;
3617 let mut scroll_position =
3618 point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
3619 let forbid_vertical_scroll = editor.scroll_manager.forbid_vertical_scroll();
3620 if forbid_vertical_scroll {
3621 scroll_position.y = current_scroll_position.y;
3622 if scroll_position == current_scroll_position {
3623 return;
3624 }
3625 }
3626 editor.scroll(scroll_position, axis, cx);
3627 cx.stop_propagation();
3628 });
3629 }
3630 }
3631 });
3632 }
3633
3634 fn paint_mouse_listeners(
3635 &mut self,
3636 layout: &EditorLayout,
3637 hovered_hunk: Option<HunkToExpand>,
3638 cx: &mut WindowContext,
3639 ) {
3640 self.paint_scroll_wheel_listener(layout, cx);
3641
3642 cx.on_mouse_event({
3643 let position_map = layout.position_map.clone();
3644 let editor = self.editor.clone();
3645 let text_hitbox = layout.text_hitbox.clone();
3646 let gutter_hitbox = layout.gutter_hitbox.clone();
3647
3648 move |event: &MouseDownEvent, phase, cx| {
3649 if phase == DispatchPhase::Bubble {
3650 match event.button {
3651 MouseButton::Left => editor.update(cx, |editor, cx| {
3652 Self::mouse_left_down(
3653 editor,
3654 event,
3655 hovered_hunk.as_ref(),
3656 &position_map,
3657 &text_hitbox,
3658 &gutter_hitbox,
3659 cx,
3660 );
3661 }),
3662 MouseButton::Right => editor.update(cx, |editor, cx| {
3663 Self::mouse_right_down(editor, event, &position_map, &text_hitbox, cx);
3664 }),
3665 MouseButton::Middle => editor.update(cx, |editor, cx| {
3666 Self::mouse_middle_down(editor, event, &position_map, &text_hitbox, cx);
3667 }),
3668 _ => {}
3669 };
3670 }
3671 }
3672 });
3673
3674 cx.on_mouse_event({
3675 let editor = self.editor.clone();
3676 let position_map = layout.position_map.clone();
3677 let text_hitbox = layout.text_hitbox.clone();
3678
3679 move |event: &MouseUpEvent, phase, cx| {
3680 if phase == DispatchPhase::Bubble {
3681 editor.update(cx, |editor, cx| {
3682 Self::mouse_up(editor, event, &position_map, &text_hitbox, cx)
3683 });
3684 }
3685 }
3686 });
3687 cx.on_mouse_event({
3688 let position_map = layout.position_map.clone();
3689 let editor = self.editor.clone();
3690 let text_hitbox = layout.text_hitbox.clone();
3691 let gutter_hitbox = layout.gutter_hitbox.clone();
3692
3693 move |event: &MouseMoveEvent, phase, cx| {
3694 if phase == DispatchPhase::Bubble {
3695 editor.update(cx, |editor, cx| {
3696 if event.pressed_button == Some(MouseButton::Left)
3697 || event.pressed_button == Some(MouseButton::Middle)
3698 {
3699 Self::mouse_dragged(
3700 editor,
3701 event,
3702 &position_map,
3703 text_hitbox.bounds,
3704 cx,
3705 )
3706 }
3707
3708 Self::mouse_moved(
3709 editor,
3710 event,
3711 &position_map,
3712 &text_hitbox,
3713 &gutter_hitbox,
3714 cx,
3715 )
3716 });
3717 }
3718 }
3719 });
3720 }
3721
3722 fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
3723 bounds.upper_right().x - self.style.scrollbar_width
3724 }
3725
3726 fn column_pixels(&self, column: usize, cx: &WindowContext) -> Pixels {
3727 let style = &self.style;
3728 let font_size = style.text.font_size.to_pixels(cx.rem_size());
3729 let layout = cx
3730 .text_system()
3731 .shape_line(
3732 SharedString::from(" ".repeat(column)),
3733 font_size,
3734 &[TextRun {
3735 len: column,
3736 font: style.text.font(),
3737 color: Hsla::default(),
3738 background_color: None,
3739 underline: None,
3740 strikethrough: None,
3741 }],
3742 )
3743 .unwrap();
3744
3745 layout.width
3746 }
3747
3748 fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &WindowContext) -> Pixels {
3749 let digit_count = snapshot
3750 .max_buffer_row()
3751 .next_row()
3752 .as_f32()
3753 .log10()
3754 .floor() as usize
3755 + 1;
3756 self.column_pixels(digit_count, cx)
3757 }
3758}
3759
3760fn prepaint_gutter_button(
3761 button: IconButton,
3762 row: DisplayRow,
3763 line_height: Pixels,
3764 gutter_dimensions: &GutterDimensions,
3765 scroll_pixel_position: gpui::Point<Pixels>,
3766 gutter_hitbox: &Hitbox,
3767 cx: &mut WindowContext<'_>,
3768) -> AnyElement {
3769 let mut button = button.into_any_element();
3770 let available_space = size(
3771 AvailableSpace::MinContent,
3772 AvailableSpace::Definite(line_height),
3773 );
3774 let indicator_size = button.layout_as_root(available_space, cx);
3775
3776 let blame_width = gutter_dimensions
3777 .git_blame_entries_width
3778 .unwrap_or(Pixels::ZERO);
3779
3780 let mut x = blame_width;
3781 let available_width = gutter_dimensions.margin + gutter_dimensions.left_padding
3782 - indicator_size.width
3783 - blame_width;
3784 x += available_width / 2.;
3785
3786 let mut y = row.as_f32() * line_height - scroll_pixel_position.y;
3787 y += (line_height - indicator_size.height) / 2.;
3788
3789 button.prepaint_as_root(gutter_hitbox.origin + point(x, y), available_space, cx);
3790 button
3791}
3792
3793fn render_inline_blame_entry(
3794 blame: &gpui::Model<GitBlame>,
3795 blame_entry: BlameEntry,
3796 style: &EditorStyle,
3797 workspace: Option<WeakView<Workspace>>,
3798 cx: &mut WindowContext<'_>,
3799) -> AnyElement {
3800 let relative_timestamp = blame_entry_relative_timestamp(&blame_entry, cx);
3801
3802 let author = blame_entry.author.as_deref().unwrap_or_default();
3803 let text = format!("{}, {}", author, relative_timestamp);
3804
3805 let details = blame.read(cx).details_for_entry(&blame_entry);
3806
3807 let tooltip = cx.new_view(|_| BlameEntryTooltip::new(blame_entry, details, style, workspace));
3808
3809 h_flex()
3810 .id("inline-blame")
3811 .w_full()
3812 .font_family(style.text.font().family)
3813 .text_color(cx.theme().status().hint)
3814 .line_height(style.text.line_height)
3815 .child(Icon::new(IconName::FileGit).color(Color::Hint))
3816 .child(text)
3817 .gap_2()
3818 .hoverable_tooltip(move |_| tooltip.clone().into())
3819 .into_any()
3820}
3821
3822fn render_blame_entry(
3823 ix: usize,
3824 blame: &gpui::Model<GitBlame>,
3825 blame_entry: BlameEntry,
3826 style: &EditorStyle,
3827 last_used_color: &mut Option<(PlayerColor, Oid)>,
3828 editor: View<Editor>,
3829 cx: &mut WindowContext<'_>,
3830) -> AnyElement {
3831 let mut sha_color = cx
3832 .theme()
3833 .players()
3834 .color_for_participant(blame_entry.sha.into());
3835 // If the last color we used is the same as the one we get for this line, but
3836 // the commit SHAs are different, then we try again to get a different color.
3837 match *last_used_color {
3838 Some((color, sha)) if sha != blame_entry.sha && color.cursor == sha_color.cursor => {
3839 let index: u32 = blame_entry.sha.into();
3840 sha_color = cx.theme().players().color_for_participant(index + 1);
3841 }
3842 _ => {}
3843 };
3844 last_used_color.replace((sha_color, blame_entry.sha));
3845
3846 let relative_timestamp = blame_entry_relative_timestamp(&blame_entry, cx);
3847
3848 let short_commit_id = blame_entry.sha.display_short();
3849
3850 let author_name = blame_entry.author.as_deref().unwrap_or("<no name>");
3851 let name = util::truncate_and_trailoff(author_name, 20);
3852
3853 let details = blame.read(cx).details_for_entry(&blame_entry);
3854
3855 let workspace = editor.read(cx).workspace.as_ref().map(|(w, _)| w.clone());
3856
3857 let tooltip = cx.new_view(|_| {
3858 BlameEntryTooltip::new(blame_entry.clone(), details.clone(), style, workspace)
3859 });
3860
3861 h_flex()
3862 .w_full()
3863 .font_family(style.text.font().family)
3864 .line_height(style.text.line_height)
3865 .id(("blame", ix))
3866 .children([
3867 div()
3868 .text_color(sha_color.cursor)
3869 .child(short_commit_id)
3870 .mr_2(),
3871 div()
3872 .w_full()
3873 .h_flex()
3874 .justify_between()
3875 .text_color(cx.theme().status().hint)
3876 .child(name)
3877 .child(relative_timestamp),
3878 ])
3879 .on_mouse_down(MouseButton::Right, {
3880 let blame_entry = blame_entry.clone();
3881 let details = details.clone();
3882 move |event, cx| {
3883 deploy_blame_entry_context_menu(
3884 &blame_entry,
3885 details.as_ref(),
3886 editor.clone(),
3887 event.position,
3888 cx,
3889 );
3890 }
3891 })
3892 .hover(|style| style.bg(cx.theme().colors().element_hover))
3893 .when_some(
3894 details.and_then(|details| details.permalink),
3895 |this, url| {
3896 let url = url.clone();
3897 this.cursor_pointer().on_click(move |_, cx| {
3898 cx.stop_propagation();
3899 cx.open_url(url.as_str())
3900 })
3901 },
3902 )
3903 .hoverable_tooltip(move |_| tooltip.clone().into())
3904 .into_any()
3905}
3906
3907fn deploy_blame_entry_context_menu(
3908 blame_entry: &BlameEntry,
3909 details: Option<&CommitDetails>,
3910 editor: View<Editor>,
3911 position: gpui::Point<Pixels>,
3912 cx: &mut WindowContext<'_>,
3913) {
3914 let context_menu = ContextMenu::build(cx, move |this, _| {
3915 let sha = format!("{}", blame_entry.sha);
3916 this.entry("Copy commit SHA", None, move |cx| {
3917 cx.write_to_clipboard(ClipboardItem::new(sha.clone()));
3918 })
3919 .when_some(
3920 details.and_then(|details| details.permalink.clone()),
3921 |this, url| this.entry("Open permalink", None, move |cx| cx.open_url(url.as_str())),
3922 )
3923 });
3924
3925 editor.update(cx, move |editor, cx| {
3926 editor.mouse_context_menu = Some(MouseContextMenu::new(position, context_menu, cx));
3927 cx.notify();
3928 });
3929}
3930
3931#[derive(Debug)]
3932pub(crate) struct LineWithInvisibles {
3933 fragments: SmallVec<[LineFragment; 1]>,
3934 invisibles: Vec<Invisible>,
3935 len: usize,
3936 width: Pixels,
3937 font_size: Pixels,
3938}
3939
3940#[allow(clippy::large_enum_variant)]
3941enum LineFragment {
3942 Text(ShapedLine),
3943 Element {
3944 element: Option<AnyElement>,
3945 size: Size<Pixels>,
3946 len: usize,
3947 },
3948}
3949
3950impl fmt::Debug for LineFragment {
3951 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3952 match self {
3953 LineFragment::Text(shaped_line) => f.debug_tuple("Text").field(shaped_line).finish(),
3954 LineFragment::Element { size, len, .. } => f
3955 .debug_struct("Element")
3956 .field("size", size)
3957 .field("len", len)
3958 .finish(),
3959 }
3960 }
3961}
3962
3963impl LineWithInvisibles {
3964 fn from_chunks<'a>(
3965 chunks: impl Iterator<Item = HighlightedChunk<'a>>,
3966 text_style: &TextStyle,
3967 max_line_len: usize,
3968 max_line_count: usize,
3969 line_number_layouts: &[Option<ShapedLine>],
3970 editor_mode: EditorMode,
3971 cx: &mut WindowContext,
3972 ) -> Vec<Self> {
3973 let mut layouts = Vec::with_capacity(max_line_count);
3974 let mut fragments: SmallVec<[LineFragment; 1]> = SmallVec::new();
3975 let mut line = String::new();
3976 let mut invisibles = Vec::new();
3977 let mut width = Pixels::ZERO;
3978 let mut len = 0;
3979 let mut styles = Vec::new();
3980 let mut non_whitespace_added = false;
3981 let mut row = 0;
3982 let mut line_exceeded_max_len = false;
3983 let font_size = text_style.font_size.to_pixels(cx.rem_size());
3984
3985 let ellipsis = SharedString::from("⋯");
3986
3987 for highlighted_chunk in chunks.chain([HighlightedChunk {
3988 text: "\n",
3989 style: None,
3990 is_tab: false,
3991 renderer: None,
3992 }]) {
3993 if let Some(renderer) = highlighted_chunk.renderer {
3994 if !line.is_empty() {
3995 let shaped_line = cx
3996 .text_system()
3997 .shape_line(line.clone().into(), font_size, &styles)
3998 .unwrap();
3999 width += shaped_line.width;
4000 len += shaped_line.len;
4001 fragments.push(LineFragment::Text(shaped_line));
4002 line.clear();
4003 styles.clear();
4004 }
4005
4006 let available_width = if renderer.constrain_width {
4007 let chunk = if highlighted_chunk.text == ellipsis.as_ref() {
4008 ellipsis.clone()
4009 } else {
4010 SharedString::from(Arc::from(highlighted_chunk.text))
4011 };
4012 let shaped_line = cx
4013 .text_system()
4014 .shape_line(
4015 chunk,
4016 font_size,
4017 &[text_style.to_run(highlighted_chunk.text.len())],
4018 )
4019 .unwrap();
4020 AvailableSpace::Definite(shaped_line.width)
4021 } else {
4022 AvailableSpace::MinContent
4023 };
4024
4025 let mut element = (renderer.render)(cx);
4026 let line_height = text_style.line_height_in_pixels(cx.rem_size());
4027 let size = element.layout_as_root(
4028 size(available_width, AvailableSpace::Definite(line_height)),
4029 cx,
4030 );
4031
4032 width += size.width;
4033 len += highlighted_chunk.text.len();
4034 fragments.push(LineFragment::Element {
4035 element: Some(element),
4036 size,
4037 len: highlighted_chunk.text.len(),
4038 });
4039 } else {
4040 for (ix, mut line_chunk) in highlighted_chunk.text.split('\n').enumerate() {
4041 if ix > 0 {
4042 let shaped_line = cx
4043 .text_system()
4044 .shape_line(line.clone().into(), font_size, &styles)
4045 .unwrap();
4046 width += shaped_line.width;
4047 len += shaped_line.len;
4048 fragments.push(LineFragment::Text(shaped_line));
4049 layouts.push(Self {
4050 width: mem::take(&mut width),
4051 len: mem::take(&mut len),
4052 fragments: mem::take(&mut fragments),
4053 invisibles: std::mem::take(&mut invisibles),
4054 font_size,
4055 });
4056
4057 line.clear();
4058 styles.clear();
4059 row += 1;
4060 line_exceeded_max_len = false;
4061 non_whitespace_added = false;
4062 if row == max_line_count {
4063 return layouts;
4064 }
4065 }
4066
4067 if !line_chunk.is_empty() && !line_exceeded_max_len {
4068 let text_style = if let Some(style) = highlighted_chunk.style {
4069 Cow::Owned(text_style.clone().highlight(style))
4070 } else {
4071 Cow::Borrowed(text_style)
4072 };
4073
4074 if line.len() + line_chunk.len() > max_line_len {
4075 let mut chunk_len = max_line_len - line.len();
4076 while !line_chunk.is_char_boundary(chunk_len) {
4077 chunk_len -= 1;
4078 }
4079 line_chunk = &line_chunk[..chunk_len];
4080 line_exceeded_max_len = true;
4081 }
4082
4083 styles.push(TextRun {
4084 len: line_chunk.len(),
4085 font: text_style.font(),
4086 color: text_style.color,
4087 background_color: text_style.background_color,
4088 underline: text_style.underline,
4089 strikethrough: text_style.strikethrough,
4090 });
4091
4092 if editor_mode == EditorMode::Full {
4093 // Line wrap pads its contents with fake whitespaces,
4094 // avoid printing them
4095 let inside_wrapped_string = line_number_layouts
4096 .get(row)
4097 .and_then(|layout| layout.as_ref())
4098 .is_none();
4099 if highlighted_chunk.is_tab {
4100 if non_whitespace_added || !inside_wrapped_string {
4101 invisibles.push(Invisible::Tab {
4102 line_start_offset: line.len(),
4103 line_end_offset: line.len() + line_chunk.len(),
4104 });
4105 }
4106 } else {
4107 invisibles.extend(
4108 line_chunk
4109 .bytes()
4110 .enumerate()
4111 .filter(|(_, line_byte)| {
4112 let is_whitespace =
4113 (*line_byte as char).is_whitespace();
4114 non_whitespace_added |= !is_whitespace;
4115 is_whitespace
4116 && (non_whitespace_added || !inside_wrapped_string)
4117 })
4118 .map(|(whitespace_index, _)| Invisible::Whitespace {
4119 line_offset: line.len() + whitespace_index,
4120 }),
4121 )
4122 }
4123 }
4124
4125 line.push_str(line_chunk);
4126 }
4127 }
4128 }
4129 }
4130
4131 layouts
4132 }
4133
4134 fn prepaint(
4135 &mut self,
4136 line_height: Pixels,
4137 scroll_pixel_position: gpui::Point<Pixels>,
4138 row: DisplayRow,
4139 content_origin: gpui::Point<Pixels>,
4140 line_elements: &mut SmallVec<[AnyElement; 1]>,
4141 cx: &mut WindowContext,
4142 ) {
4143 let line_y = line_height * (row.as_f32() - scroll_pixel_position.y / line_height);
4144 let mut fragment_origin = content_origin + gpui::point(-scroll_pixel_position.x, line_y);
4145 for fragment in &mut self.fragments {
4146 match fragment {
4147 LineFragment::Text(line) => {
4148 fragment_origin.x += line.width;
4149 }
4150 LineFragment::Element { element, size, .. } => {
4151 let mut element = element
4152 .take()
4153 .expect("you can't prepaint LineWithInvisibles twice");
4154
4155 // Center the element vertically within the line.
4156 let mut element_origin = fragment_origin;
4157 element_origin.y += (line_height - size.height) / 2.;
4158 element.prepaint_at(element_origin, cx);
4159 line_elements.push(element);
4160
4161 fragment_origin.x += size.width;
4162 }
4163 }
4164 }
4165 }
4166
4167 fn draw(
4168 &self,
4169 layout: &EditorLayout,
4170 row: DisplayRow,
4171 content_origin: gpui::Point<Pixels>,
4172 whitespace_setting: ShowWhitespaceSetting,
4173 selection_ranges: &[Range<DisplayPoint>],
4174 cx: &mut WindowContext,
4175 ) {
4176 let line_height = layout.position_map.line_height;
4177 let line_y = line_height
4178 * (row.as_f32() - layout.position_map.scroll_pixel_position.y / line_height);
4179
4180 let mut fragment_origin =
4181 content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y);
4182
4183 for fragment in &self.fragments {
4184 match fragment {
4185 LineFragment::Text(line) => {
4186 line.paint(fragment_origin, line_height, cx).log_err();
4187 fragment_origin.x += line.width;
4188 }
4189 LineFragment::Element { size, .. } => {
4190 fragment_origin.x += size.width;
4191 }
4192 }
4193 }
4194
4195 self.draw_invisibles(
4196 &selection_ranges,
4197 layout,
4198 content_origin,
4199 line_y,
4200 row,
4201 line_height,
4202 whitespace_setting,
4203 cx,
4204 );
4205 }
4206
4207 #[allow(clippy::too_many_arguments)]
4208 fn draw_invisibles(
4209 &self,
4210 selection_ranges: &[Range<DisplayPoint>],
4211 layout: &EditorLayout,
4212 content_origin: gpui::Point<Pixels>,
4213 line_y: Pixels,
4214 row: DisplayRow,
4215 line_height: Pixels,
4216 whitespace_setting: ShowWhitespaceSetting,
4217 cx: &mut WindowContext,
4218 ) {
4219 let extract_whitespace_info = |invisible: &Invisible| {
4220 let (token_offset, token_end_offset, invisible_symbol) = match invisible {
4221 Invisible::Tab {
4222 line_start_offset,
4223 line_end_offset,
4224 } => (*line_start_offset, *line_end_offset, &layout.tab_invisible),
4225 Invisible::Whitespace { line_offset } => {
4226 (*line_offset, line_offset + 1, &layout.space_invisible)
4227 }
4228 };
4229
4230 let x_offset = self.x_for_index(token_offset);
4231 let invisible_offset =
4232 (layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
4233 let origin = content_origin
4234 + gpui::point(
4235 x_offset + invisible_offset - layout.position_map.scroll_pixel_position.x,
4236 line_y,
4237 );
4238
4239 (
4240 [token_offset, token_end_offset],
4241 Box::new(move |cx: &mut WindowContext| {
4242 invisible_symbol.paint(origin, line_height, cx).log_err();
4243 }),
4244 )
4245 };
4246
4247 let invisible_iter = self.invisibles.iter().map(extract_whitespace_info);
4248 match whitespace_setting {
4249 ShowWhitespaceSetting::None => return,
4250 ShowWhitespaceSetting::All => invisible_iter.for_each(|(_, paint)| paint(cx)),
4251 ShowWhitespaceSetting::Selection => invisible_iter.for_each(|([start, _], paint)| {
4252 let invisible_point = DisplayPoint::new(row, start as u32);
4253 if !selection_ranges
4254 .iter()
4255 .any(|region| region.start <= invisible_point && invisible_point < region.end)
4256 {
4257 return;
4258 }
4259
4260 paint(cx);
4261 }),
4262
4263 // For a whitespace to be on a boundary, any of the following conditions need to be met:
4264 // - It is a tab
4265 // - It is adjacent to an edge (start or end)
4266 // - It is adjacent to a whitespace (left or right)
4267 ShowWhitespaceSetting::Boundary => {
4268 // We'll need to keep track of the last invisible we've seen and then check if we are adjacent to it for some of
4269 // the above cases.
4270 // Note: We zip in the original `invisibles` to check for tab equality
4271 let mut last_seen: Option<(bool, usize, Box<dyn Fn(&mut WindowContext)>)> = None;
4272 for (([start, end], paint), invisible) in
4273 invisible_iter.zip_eq(self.invisibles.iter())
4274 {
4275 let should_render = match (&last_seen, invisible) {
4276 (_, Invisible::Tab { .. }) => true,
4277 (Some((_, last_end, _)), _) => *last_end == start,
4278 _ => false,
4279 };
4280
4281 if should_render || start == 0 || end == self.len {
4282 paint(cx);
4283
4284 // Since we are scanning from the left, we will skip over the first available whitespace that is part
4285 // of a boundary between non-whitespace segments, so we correct by manually redrawing it if needed.
4286 if let Some((should_render_last, last_end, paint_last)) = last_seen {
4287 // Note that we need to make sure that the last one is actually adjacent
4288 if !should_render_last && last_end == start {
4289 paint_last(cx);
4290 }
4291 }
4292 }
4293
4294 // Manually render anything within a selection
4295 let invisible_point = DisplayPoint::new(row, start as u32);
4296 if selection_ranges.iter().any(|region| {
4297 region.start <= invisible_point && invisible_point < region.end
4298 }) {
4299 paint(cx);
4300 }
4301
4302 last_seen = Some((should_render, end, paint));
4303 }
4304 }
4305 };
4306 }
4307
4308 pub fn x_for_index(&self, index: usize) -> Pixels {
4309 let mut fragment_start_x = Pixels::ZERO;
4310 let mut fragment_start_index = 0;
4311
4312 for fragment in &self.fragments {
4313 match fragment {
4314 LineFragment::Text(shaped_line) => {
4315 let fragment_end_index = fragment_start_index + shaped_line.len;
4316 if index < fragment_end_index {
4317 return fragment_start_x
4318 + shaped_line.x_for_index(index - fragment_start_index);
4319 }
4320 fragment_start_x += shaped_line.width;
4321 fragment_start_index = fragment_end_index;
4322 }
4323 LineFragment::Element { len, size, .. } => {
4324 let fragment_end_index = fragment_start_index + len;
4325 if index < fragment_end_index {
4326 return fragment_start_x;
4327 }
4328 fragment_start_x += size.width;
4329 fragment_start_index = fragment_end_index;
4330 }
4331 }
4332 }
4333
4334 fragment_start_x
4335 }
4336
4337 pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
4338 let mut fragment_start_x = Pixels::ZERO;
4339 let mut fragment_start_index = 0;
4340
4341 for fragment in &self.fragments {
4342 match fragment {
4343 LineFragment::Text(shaped_line) => {
4344 let fragment_end_x = fragment_start_x + shaped_line.width;
4345 if x < fragment_end_x {
4346 return Some(
4347 fragment_start_index + shaped_line.index_for_x(x - fragment_start_x)?,
4348 );
4349 }
4350 fragment_start_x = fragment_end_x;
4351 fragment_start_index += shaped_line.len;
4352 }
4353 LineFragment::Element { len, size, .. } => {
4354 let fragment_end_x = fragment_start_x + size.width;
4355 if x < fragment_end_x {
4356 return Some(fragment_start_index);
4357 }
4358 fragment_start_index += len;
4359 fragment_start_x = fragment_end_x;
4360 }
4361 }
4362 }
4363
4364 None
4365 }
4366
4367 pub fn font_id_for_index(&self, index: usize) -> Option<FontId> {
4368 let mut fragment_start_index = 0;
4369
4370 for fragment in &self.fragments {
4371 match fragment {
4372 LineFragment::Text(shaped_line) => {
4373 let fragment_end_index = fragment_start_index + shaped_line.len;
4374 if index < fragment_end_index {
4375 return shaped_line.font_id_for_index(index - fragment_start_index);
4376 }
4377 fragment_start_index = fragment_end_index;
4378 }
4379 LineFragment::Element { len, .. } => {
4380 let fragment_end_index = fragment_start_index + len;
4381 if index < fragment_end_index {
4382 return None;
4383 }
4384 fragment_start_index = fragment_end_index;
4385 }
4386 }
4387 }
4388
4389 None
4390 }
4391}
4392
4393#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4394enum Invisible {
4395 /// A tab character
4396 ///
4397 /// A tab character is internally represented by spaces (configured by the user's tab width)
4398 /// aligned to the nearest column, so it's necessary to store the start and end offset for
4399 /// adjacency checks.
4400 Tab {
4401 line_start_offset: usize,
4402 line_end_offset: usize,
4403 },
4404 Whitespace {
4405 line_offset: usize,
4406 },
4407}
4408
4409impl EditorElement {
4410 /// Returns the rem size to use when rendering the [`EditorElement`].
4411 ///
4412 /// This allows UI elements to scale based on the `buffer_font_size`.
4413 fn rem_size(&self, cx: &WindowContext) -> Option<Pixels> {
4414 match self.editor.read(cx).mode {
4415 EditorMode::Full => {
4416 let buffer_font_size = self.style.text.font_size;
4417 match buffer_font_size {
4418 AbsoluteLength::Pixels(pixels) => {
4419 let rem_size_scale = {
4420 // Our default UI font size is 14px on a 16px base scale.
4421 // This means the default UI font size is 0.875rems.
4422 let default_font_size_scale = 14. / ui::BASE_REM_SIZE_IN_PX;
4423
4424 // We then determine the delta between a single rem and the default font
4425 // size scale.
4426 let default_font_size_delta = 1. - default_font_size_scale;
4427
4428 // Finally, we add this delta to 1rem to get the scale factor that
4429 // should be used to scale up the UI.
4430 1. + default_font_size_delta
4431 };
4432
4433 Some(pixels * rem_size_scale)
4434 }
4435 AbsoluteLength::Rems(rems) => {
4436 Some(rems.to_pixels(ui::BASE_REM_SIZE_IN_PX.into()))
4437 }
4438 }
4439 }
4440 // We currently use single-line and auto-height editors in UI contexts,
4441 // so we don't want to scale everything with the buffer font size, as it
4442 // ends up looking off.
4443 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => None,
4444 }
4445 }
4446}
4447
4448impl Element for EditorElement {
4449 type RequestLayoutState = ();
4450 type PrepaintState = EditorLayout;
4451
4452 fn id(&self) -> Option<ElementId> {
4453 None
4454 }
4455
4456 fn request_layout(
4457 &mut self,
4458 _: Option<&GlobalElementId>,
4459 cx: &mut WindowContext,
4460 ) -> (gpui::LayoutId, ()) {
4461 let rem_size = self.rem_size(cx);
4462 cx.with_rem_size(rem_size, |cx| {
4463 self.editor.update(cx, |editor, cx| {
4464 editor.set_style(self.style.clone(), cx);
4465
4466 let layout_id = match editor.mode {
4467 EditorMode::SingleLine => {
4468 let rem_size = cx.rem_size();
4469 let mut style = Style::default();
4470 style.size.width = relative(1.).into();
4471 style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
4472 cx.request_layout(style, None)
4473 }
4474 EditorMode::AutoHeight { max_lines } => {
4475 let editor_handle = cx.view().clone();
4476 let max_line_number_width =
4477 self.max_line_number_width(&editor.snapshot(cx), cx);
4478 cx.request_measured_layout(
4479 Style::default(),
4480 move |known_dimensions, available_space, cx| {
4481 editor_handle
4482 .update(cx, |editor, cx| {
4483 compute_auto_height_layout(
4484 editor,
4485 max_lines,
4486 max_line_number_width,
4487 known_dimensions,
4488 available_space.width,
4489 cx,
4490 )
4491 })
4492 .unwrap_or_default()
4493 },
4494 )
4495 }
4496 EditorMode::Full => {
4497 let mut style = Style::default();
4498 style.size.width = relative(1.).into();
4499 style.size.height = relative(1.).into();
4500 cx.request_layout(style, None)
4501 }
4502 };
4503
4504 (layout_id, ())
4505 })
4506 })
4507 }
4508
4509 fn prepaint(
4510 &mut self,
4511 _: Option<&GlobalElementId>,
4512 bounds: Bounds<Pixels>,
4513 _: &mut Self::RequestLayoutState,
4514 cx: &mut WindowContext,
4515 ) -> Self::PrepaintState {
4516 let text_style = TextStyleRefinement {
4517 font_size: Some(self.style.text.font_size),
4518 line_height: Some(self.style.text.line_height),
4519 ..Default::default()
4520 };
4521 cx.set_view_id(self.editor.entity_id());
4522
4523 let rem_size = self.rem_size(cx);
4524 cx.with_rem_size(rem_size, |cx| {
4525 cx.with_text_style(Some(text_style), |cx| {
4526 cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
4527 let mut snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
4528 let style = self.style.clone();
4529
4530 let font_id = cx.text_system().resolve_font(&style.text.font());
4531 let font_size = style.text.font_size.to_pixels(cx.rem_size());
4532 let line_height = style.text.line_height_in_pixels(cx.rem_size());
4533 let em_width = cx
4534 .text_system()
4535 .typographic_bounds(font_id, font_size, 'm')
4536 .unwrap()
4537 .size
4538 .width;
4539 let em_advance = cx
4540 .text_system()
4541 .advance(font_id, font_size, 'm')
4542 .unwrap()
4543 .width;
4544
4545 let gutter_dimensions = snapshot.gutter_dimensions(
4546 font_id,
4547 font_size,
4548 em_width,
4549 self.max_line_number_width(&snapshot, cx),
4550 cx,
4551 );
4552 let text_width = bounds.size.width - gutter_dimensions.width;
4553
4554 let right_margin = if snapshot.mode == EditorMode::Full {
4555 EditorElement::SCROLLBAR_WIDTH
4556 } else {
4557 px(0.)
4558 };
4559 let overscroll = size(em_width + right_margin, px(0.));
4560
4561 snapshot = self.editor.update(cx, |editor, cx| {
4562 editor.last_bounds = Some(bounds);
4563 editor.gutter_dimensions = gutter_dimensions;
4564 editor.set_visible_line_count(bounds.size.height / line_height, cx);
4565
4566 let editor_width =
4567 text_width - gutter_dimensions.margin - overscroll.width - em_width;
4568 let wrap_width = match editor.soft_wrap_mode(cx) {
4569 SoftWrap::None => None,
4570 SoftWrap::PreferLine => Some((MAX_LINE_LEN / 2) as f32 * em_advance),
4571 SoftWrap::EditorWidth => Some(editor_width),
4572 SoftWrap::Column(column) => {
4573 Some(editor_width.min(column as f32 * em_advance))
4574 }
4575 };
4576
4577 if editor.set_wrap_width(wrap_width, cx) {
4578 editor.snapshot(cx)
4579 } else {
4580 snapshot
4581 }
4582 });
4583
4584 let wrap_guides = self
4585 .editor
4586 .read(cx)
4587 .wrap_guides(cx)
4588 .iter()
4589 .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
4590 .collect::<SmallVec<[_; 2]>>();
4591
4592 let hitbox = cx.insert_hitbox(bounds, false);
4593 let gutter_hitbox = cx.insert_hitbox(
4594 Bounds {
4595 origin: bounds.origin,
4596 size: size(gutter_dimensions.width, bounds.size.height),
4597 },
4598 false,
4599 );
4600 let text_hitbox = cx.insert_hitbox(
4601 Bounds {
4602 origin: gutter_hitbox.upper_right(),
4603 size: size(text_width, bounds.size.height),
4604 },
4605 false,
4606 );
4607 // Offset the content_bounds from the text_bounds by the gutter margin (which
4608 // is roughly half a character wide) to make hit testing work more like how we want.
4609 let content_origin =
4610 text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
4611
4612 let mut autoscroll_containing_element = false;
4613 let mut autoscroll_horizontally = false;
4614 self.editor.update(cx, |editor, cx| {
4615 autoscroll_containing_element =
4616 editor.autoscroll_requested() || editor.has_pending_selection();
4617 autoscroll_horizontally =
4618 editor.autoscroll_vertically(bounds, line_height, cx);
4619 snapshot = editor.snapshot(cx);
4620 });
4621
4622 let mut scroll_position = snapshot.scroll_position();
4623 // The scroll position is a fractional point, the whole number of which represents
4624 // the top of the window in terms of display rows.
4625 let start_row = DisplayRow(scroll_position.y as u32);
4626 let height_in_lines = bounds.size.height / line_height;
4627 let max_row = snapshot.max_point().row();
4628 let end_row = cmp::min(
4629 (scroll_position.y + height_in_lines).ceil() as u32,
4630 max_row.next_row().0,
4631 );
4632 let end_row = DisplayRow(end_row);
4633
4634 let buffer_rows = snapshot
4635 .buffer_rows(start_row)
4636 .take((start_row..end_row).len())
4637 .collect::<Vec<_>>();
4638
4639 let start_anchor = if start_row == Default::default() {
4640 Anchor::min()
4641 } else {
4642 snapshot.buffer_snapshot.anchor_before(
4643 DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left),
4644 )
4645 };
4646 let end_anchor = if end_row > max_row {
4647 Anchor::max()
4648 } else {
4649 snapshot.buffer_snapshot.anchor_before(
4650 DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right),
4651 )
4652 };
4653
4654 let highlighted_rows = self
4655 .editor
4656 .update(cx, |editor, cx| editor.highlighted_display_rows(cx));
4657 let highlighted_ranges = self.editor.read(cx).background_highlights_in_range(
4658 start_anchor..end_anchor,
4659 &snapshot.display_snapshot,
4660 cx.theme().colors(),
4661 );
4662 let highlighted_gutter_ranges =
4663 self.editor.read(cx).gutter_highlights_in_range(
4664 start_anchor..end_anchor,
4665 &snapshot.display_snapshot,
4666 cx,
4667 );
4668
4669 let redacted_ranges = self.editor.read(cx).redacted_ranges(
4670 start_anchor..end_anchor,
4671 &snapshot.display_snapshot,
4672 cx,
4673 );
4674
4675 let (selections, active_rows, newest_selection_head) = self.layout_selections(
4676 start_anchor,
4677 end_anchor,
4678 &snapshot,
4679 start_row,
4680 end_row,
4681 cx,
4682 );
4683
4684 let line_numbers = self.layout_line_numbers(
4685 start_row..end_row,
4686 buffer_rows.iter().copied(),
4687 &active_rows,
4688 newest_selection_head,
4689 &snapshot,
4690 cx,
4691 );
4692
4693 let mut gutter_fold_toggles =
4694 cx.with_element_namespace("gutter_fold_toggles", |cx| {
4695 self.layout_gutter_fold_toggles(
4696 start_row..end_row,
4697 buffer_rows.iter().copied(),
4698 &active_rows,
4699 &snapshot,
4700 cx,
4701 )
4702 });
4703 let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| {
4704 self.layout_crease_trailers(buffer_rows.iter().copied(), &snapshot, cx)
4705 });
4706
4707 let display_hunks = self.layout_git_gutters(
4708 line_height,
4709 &gutter_hitbox,
4710 start_row..end_row,
4711 &snapshot,
4712 cx,
4713 );
4714
4715 let mut max_visible_line_width = Pixels::ZERO;
4716 let mut line_layouts =
4717 self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
4718 for line_with_invisibles in &line_layouts {
4719 if line_with_invisibles.width > max_visible_line_width {
4720 max_visible_line_width = line_with_invisibles.width;
4721 }
4722 }
4723
4724 let longest_line_width =
4725 layout_line(snapshot.longest_row(), &snapshot, &style, cx).width;
4726 let mut scroll_width =
4727 longest_line_width.max(max_visible_line_width) + overscroll.width;
4728
4729 let mut blocks = cx.with_element_namespace("blocks", |cx| {
4730 self.build_blocks(
4731 start_row..end_row,
4732 &snapshot,
4733 &hitbox,
4734 &text_hitbox,
4735 &mut scroll_width,
4736 &gutter_dimensions,
4737 em_width,
4738 gutter_dimensions.full_width(),
4739 line_height,
4740 &line_layouts,
4741 cx,
4742 )
4743 });
4744
4745 let scroll_pixel_position = point(
4746 scroll_position.x * em_width,
4747 scroll_position.y * line_height,
4748 );
4749
4750 let start_buffer_row =
4751 MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
4752 let end_buffer_row =
4753 MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
4754
4755 let indent_guides = self.layout_indent_guides(
4756 content_origin,
4757 text_hitbox.origin,
4758 start_buffer_row..end_buffer_row,
4759 scroll_pixel_position,
4760 line_height,
4761 &snapshot,
4762 cx,
4763 );
4764
4765 let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| {
4766 self.prepaint_crease_trailers(
4767 crease_trailers,
4768 &line_layouts,
4769 line_height,
4770 content_origin,
4771 scroll_pixel_position,
4772 em_width,
4773 cx,
4774 )
4775 });
4776
4777 let mut inline_blame = None;
4778 if let Some(newest_selection_head) = newest_selection_head {
4779 let display_row = newest_selection_head.row();
4780 if (start_row..end_row).contains(&display_row) {
4781 let line_ix = display_row.minus(start_row) as usize;
4782 let line_layout = &line_layouts[line_ix];
4783 let crease_trailer_layout = crease_trailers[line_ix].as_ref();
4784 inline_blame = self.layout_inline_blame(
4785 display_row,
4786 &snapshot.display_snapshot,
4787 line_layout,
4788 crease_trailer_layout,
4789 em_width,
4790 content_origin,
4791 scroll_pixel_position,
4792 line_height,
4793 cx,
4794 );
4795 }
4796 }
4797
4798 let blamed_display_rows = self.layout_blame_entries(
4799 buffer_rows.into_iter(),
4800 em_width,
4801 scroll_position,
4802 line_height,
4803 &gutter_hitbox,
4804 gutter_dimensions.git_blame_entries_width,
4805 cx,
4806 );
4807
4808 let scroll_max = point(
4809 ((scroll_width - text_hitbox.size.width) / em_width).max(0.0),
4810 max_row.as_f32(),
4811 );
4812
4813 self.editor.update(cx, |editor, cx| {
4814 let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
4815
4816 let autoscrolled = if autoscroll_horizontally {
4817 editor.autoscroll_horizontally(
4818 start_row,
4819 text_hitbox.size.width,
4820 scroll_width,
4821 em_width,
4822 &line_layouts,
4823 cx,
4824 )
4825 } else {
4826 false
4827 };
4828
4829 if clamped || autoscrolled {
4830 snapshot = editor.snapshot(cx);
4831 scroll_position = snapshot.scroll_position();
4832 }
4833 });
4834
4835 let line_elements = self.prepaint_lines(
4836 start_row,
4837 &mut line_layouts,
4838 line_height,
4839 scroll_pixel_position,
4840 content_origin,
4841 cx,
4842 );
4843
4844 cx.with_element_namespace("blocks", |cx| {
4845 self.layout_blocks(
4846 &mut blocks,
4847 &hitbox,
4848 line_height,
4849 scroll_pixel_position,
4850 cx,
4851 );
4852 });
4853
4854 let cursors = self.collect_cursors(&snapshot, cx);
4855 let visible_row_range = start_row..end_row;
4856 let non_visible_cursors = cursors
4857 .iter()
4858 .any(move |c| !visible_row_range.contains(&c.0.row()));
4859
4860 let visible_cursors = self.layout_visible_cursors(
4861 &snapshot,
4862 &selections,
4863 start_row..end_row,
4864 &line_layouts,
4865 &text_hitbox,
4866 content_origin,
4867 scroll_position,
4868 scroll_pixel_position,
4869 line_height,
4870 em_width,
4871 autoscroll_containing_element,
4872 cx,
4873 );
4874
4875 let scrollbar_layout = self.layout_scrollbar(
4876 &snapshot,
4877 bounds,
4878 scroll_position,
4879 height_in_lines,
4880 non_visible_cursors,
4881 cx,
4882 );
4883
4884 let gutter_settings = EditorSettings::get_global(cx).gutter;
4885
4886 let mut _context_menu_visible = false;
4887 let mut code_actions_indicator = None;
4888 if let Some(newest_selection_head) = newest_selection_head {
4889 if (start_row..end_row).contains(&newest_selection_head.row()) {
4890 _context_menu_visible = self.layout_context_menu(
4891 line_height,
4892 &hitbox,
4893 &text_hitbox,
4894 content_origin,
4895 start_row,
4896 scroll_pixel_position,
4897 &line_layouts,
4898 newest_selection_head,
4899 gutter_dimensions.width - gutter_dimensions.left_padding,
4900 cx,
4901 );
4902
4903 let show_code_actions = snapshot
4904 .show_code_actions
4905 .unwrap_or_else(|| gutter_settings.code_actions);
4906 if show_code_actions {
4907 let newest_selection_point =
4908 newest_selection_head.to_point(&snapshot.display_snapshot);
4909 let buffer = snapshot.buffer_snapshot.buffer_line_for_row(
4910 MultiBufferRow(newest_selection_point.row),
4911 );
4912 if let Some((buffer, range)) = buffer {
4913 let buffer_id = buffer.remote_id();
4914 let row = range.start.row;
4915 let has_test_indicator =
4916 self.editor.read(cx).tasks.contains_key(&(buffer_id, row));
4917
4918 if !has_test_indicator {
4919 code_actions_indicator = self
4920 .layout_code_actions_indicator(
4921 line_height,
4922 newest_selection_head,
4923 scroll_pixel_position,
4924 &gutter_dimensions,
4925 &gutter_hitbox,
4926 cx,
4927 );
4928 }
4929 }
4930 }
4931 }
4932 }
4933
4934 let test_indicators = self.layout_run_indicators(
4935 line_height,
4936 scroll_pixel_position,
4937 &gutter_dimensions,
4938 &gutter_hitbox,
4939 &snapshot,
4940 cx,
4941 );
4942
4943 if !cx.has_active_drag() {
4944 self.layout_hover_popovers(
4945 &snapshot,
4946 &hitbox,
4947 &text_hitbox,
4948 start_row..end_row,
4949 content_origin,
4950 scroll_pixel_position,
4951 &line_layouts,
4952 line_height,
4953 em_width,
4954 cx,
4955 );
4956 }
4957
4958 let mouse_context_menu = self.layout_mouse_context_menu(cx);
4959
4960 cx.with_element_namespace("gutter_fold_toggles", |cx| {
4961 self.prepaint_gutter_fold_toggles(
4962 &mut gutter_fold_toggles,
4963 line_height,
4964 &gutter_dimensions,
4965 gutter_settings,
4966 scroll_pixel_position,
4967 &gutter_hitbox,
4968 cx,
4969 )
4970 });
4971
4972 let invisible_symbol_font_size = font_size / 2.;
4973 let tab_invisible = cx
4974 .text_system()
4975 .shape_line(
4976 "→".into(),
4977 invisible_symbol_font_size,
4978 &[TextRun {
4979 len: "→".len(),
4980 font: self.style.text.font(),
4981 color: cx.theme().colors().editor_invisible,
4982 background_color: None,
4983 underline: None,
4984 strikethrough: None,
4985 }],
4986 )
4987 .unwrap();
4988 let space_invisible = cx
4989 .text_system()
4990 .shape_line(
4991 "•".into(),
4992 invisible_symbol_font_size,
4993 &[TextRun {
4994 len: "•".len(),
4995 font: self.style.text.font(),
4996 color: cx.theme().colors().editor_invisible,
4997 background_color: None,
4998 underline: None,
4999 strikethrough: None,
5000 }],
5001 )
5002 .unwrap();
5003
5004 EditorLayout {
5005 mode: snapshot.mode,
5006 position_map: Arc::new(PositionMap {
5007 size: bounds.size,
5008 scroll_pixel_position,
5009 scroll_max,
5010 line_layouts,
5011 line_height,
5012 em_width,
5013 em_advance,
5014 snapshot,
5015 }),
5016 visible_display_row_range: start_row..end_row,
5017 wrap_guides,
5018 indent_guides,
5019 hitbox,
5020 text_hitbox,
5021 gutter_hitbox,
5022 gutter_dimensions,
5023 content_origin,
5024 scrollbar_layout,
5025 active_rows,
5026 highlighted_rows,
5027 highlighted_ranges,
5028 highlighted_gutter_ranges,
5029 redacted_ranges,
5030 line_elements,
5031 line_numbers,
5032 display_hunks,
5033 blamed_display_rows,
5034 inline_blame,
5035 blocks,
5036 cursors,
5037 visible_cursors,
5038 selections,
5039 mouse_context_menu,
5040 test_indicators,
5041 code_actions_indicator,
5042 gutter_fold_toggles,
5043 crease_trailers,
5044 tab_invisible,
5045 space_invisible,
5046 }
5047 })
5048 })
5049 })
5050 }
5051
5052 fn paint(
5053 &mut self,
5054 _: Option<&GlobalElementId>,
5055 bounds: Bounds<gpui::Pixels>,
5056 _: &mut Self::RequestLayoutState,
5057 layout: &mut Self::PrepaintState,
5058 cx: &mut WindowContext,
5059 ) {
5060 let focus_handle = self.editor.focus_handle(cx);
5061 let key_context = self.editor.read(cx).key_context(cx);
5062 cx.set_focus_handle(&focus_handle);
5063 cx.set_key_context(key_context);
5064 cx.handle_input(
5065 &focus_handle,
5066 ElementInputHandler::new(bounds, self.editor.clone()),
5067 );
5068 self.register_actions(cx);
5069 self.register_key_listeners(cx, layout);
5070
5071 let text_style = TextStyleRefinement {
5072 font_size: Some(self.style.text.font_size),
5073 line_height: Some(self.style.text.line_height),
5074 ..Default::default()
5075 };
5076 let mouse_position = cx.mouse_position();
5077 let hovered_hunk = layout
5078 .display_hunks
5079 .iter()
5080 .find_map(|(hunk, hunk_hitbox)| match hunk {
5081 DisplayDiffHunk::Folded { .. } => None,
5082 DisplayDiffHunk::Unfolded {
5083 diff_base_byte_range,
5084 multi_buffer_range,
5085 status,
5086 ..
5087 } => {
5088 if hunk_hitbox
5089 .as_ref()
5090 .map(|hitbox| hitbox.contains(&mouse_position))
5091 .unwrap_or(false)
5092 {
5093 Some(HunkToExpand {
5094 status: *status,
5095 multi_buffer_range: multi_buffer_range.clone(),
5096 diff_base_byte_range: diff_base_byte_range.clone(),
5097 })
5098 } else {
5099 None
5100 }
5101 }
5102 });
5103 let rem_size = self.rem_size(cx);
5104 cx.with_rem_size(rem_size, |cx| {
5105 cx.with_text_style(Some(text_style), |cx| {
5106 cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
5107 self.paint_mouse_listeners(layout, hovered_hunk, cx);
5108 self.paint_background(layout, cx);
5109 self.paint_indent_guides(layout, cx);
5110
5111 if layout.gutter_hitbox.size.width > Pixels::ZERO {
5112 self.paint_blamed_display_rows(layout, cx);
5113 self.paint_line_numbers(layout, cx);
5114 }
5115
5116 self.paint_text(layout, cx);
5117
5118 if !layout.blocks.is_empty() {
5119 cx.with_element_namespace("blocks", |cx| {
5120 self.paint_blocks(layout, cx);
5121 });
5122 }
5123
5124 if layout.gutter_hitbox.size.width > Pixels::ZERO {
5125 self.paint_gutter_highlights(layout, cx);
5126 self.paint_gutter_indicators(layout, cx);
5127 }
5128
5129 self.paint_scrollbar(layout, cx);
5130 self.paint_mouse_context_menu(layout, cx);
5131 });
5132 })
5133 })
5134 }
5135}
5136
5137impl IntoElement for EditorElement {
5138 type Element = Self;
5139
5140 fn into_element(self) -> Self::Element {
5141 self
5142 }
5143}
5144
5145pub struct EditorLayout {
5146 position_map: Arc<PositionMap>,
5147 hitbox: Hitbox,
5148 text_hitbox: Hitbox,
5149 gutter_hitbox: Hitbox,
5150 gutter_dimensions: GutterDimensions,
5151 content_origin: gpui::Point<Pixels>,
5152 scrollbar_layout: Option<ScrollbarLayout>,
5153 mode: EditorMode,
5154 wrap_guides: SmallVec<[(Pixels, bool); 2]>,
5155 indent_guides: Option<Vec<IndentGuideLayout>>,
5156 visible_display_row_range: Range<DisplayRow>,
5157 active_rows: BTreeMap<DisplayRow, bool>,
5158 highlighted_rows: BTreeMap<DisplayRow, Hsla>,
5159 line_elements: SmallVec<[AnyElement; 1]>,
5160 line_numbers: Vec<Option<ShapedLine>>,
5161 display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
5162 blamed_display_rows: Option<Vec<AnyElement>>,
5163 inline_blame: Option<AnyElement>,
5164 blocks: Vec<BlockLayout>,
5165 highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
5166 highlighted_gutter_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
5167 redacted_ranges: Vec<Range<DisplayPoint>>,
5168 cursors: Vec<(DisplayPoint, Hsla)>,
5169 visible_cursors: Vec<CursorLayout>,
5170 selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
5171 code_actions_indicator: Option<AnyElement>,
5172 test_indicators: Vec<AnyElement>,
5173 gutter_fold_toggles: Vec<Option<AnyElement>>,
5174 crease_trailers: Vec<Option<CreaseTrailerLayout>>,
5175 mouse_context_menu: Option<AnyElement>,
5176 tab_invisible: ShapedLine,
5177 space_invisible: ShapedLine,
5178}
5179
5180impl EditorLayout {
5181 fn line_end_overshoot(&self) -> Pixels {
5182 0.15 * self.position_map.line_height
5183 }
5184}
5185
5186struct ColoredRange<T> {
5187 start: T,
5188 end: T,
5189 color: Hsla,
5190}
5191
5192#[derive(Clone)]
5193struct ScrollbarLayout {
5194 hitbox: Hitbox,
5195 visible_row_range: Range<f32>,
5196 visible: bool,
5197 row_height: Pixels,
5198 thumb_height: Pixels,
5199}
5200
5201impl ScrollbarLayout {
5202 const BORDER_WIDTH: Pixels = px(1.0);
5203 const LINE_MARKER_HEIGHT: Pixels = px(2.0);
5204 const MIN_MARKER_HEIGHT: Pixels = px(5.0);
5205 const MIN_THUMB_HEIGHT: Pixels = px(20.0);
5206
5207 fn thumb_bounds(&self) -> Bounds<Pixels> {
5208 let thumb_top = self.y_for_row(self.visible_row_range.start);
5209 let thumb_bottom = thumb_top + self.thumb_height;
5210 Bounds::from_corners(
5211 point(self.hitbox.left(), thumb_top),
5212 point(self.hitbox.right(), thumb_bottom),
5213 )
5214 }
5215
5216 fn y_for_row(&self, row: f32) -> Pixels {
5217 self.hitbox.top() + row * self.row_height
5218 }
5219
5220 fn marker_quads_for_ranges(
5221 &self,
5222 row_ranges: impl IntoIterator<Item = ColoredRange<DisplayRow>>,
5223 column: Option<usize>,
5224 ) -> Vec<PaintQuad> {
5225 struct MinMax {
5226 min: Pixels,
5227 max: Pixels,
5228 }
5229 let (x_range, height_limit) = if let Some(column) = column {
5230 let column_width = px(((self.hitbox.size.width - Self::BORDER_WIDTH).0 / 3.0).floor());
5231 let start = Self::BORDER_WIDTH + (column as f32 * column_width);
5232 let end = start + column_width;
5233 (
5234 Range { start, end },
5235 MinMax {
5236 min: Self::MIN_MARKER_HEIGHT,
5237 max: px(f32::MAX),
5238 },
5239 )
5240 } else {
5241 (
5242 Range {
5243 start: Self::BORDER_WIDTH,
5244 end: self.hitbox.size.width,
5245 },
5246 MinMax {
5247 min: Self::LINE_MARKER_HEIGHT,
5248 max: Self::LINE_MARKER_HEIGHT,
5249 },
5250 )
5251 };
5252
5253 let row_to_y = |row: DisplayRow| row.as_f32() * self.row_height;
5254 let mut pixel_ranges = row_ranges
5255 .into_iter()
5256 .map(|range| {
5257 let start_y = row_to_y(range.start);
5258 let end_y = row_to_y(range.end)
5259 + self.row_height.max(height_limit.min).min(height_limit.max);
5260 ColoredRange {
5261 start: start_y,
5262 end: end_y,
5263 color: range.color,
5264 }
5265 })
5266 .peekable();
5267
5268 let mut quads = Vec::new();
5269 while let Some(mut pixel_range) = pixel_ranges.next() {
5270 while let Some(next_pixel_range) = pixel_ranges.peek() {
5271 if pixel_range.end >= next_pixel_range.start - px(1.0)
5272 && pixel_range.color == next_pixel_range.color
5273 {
5274 pixel_range.end = next_pixel_range.end.max(pixel_range.end);
5275 pixel_ranges.next();
5276 } else {
5277 break;
5278 }
5279 }
5280
5281 let bounds = Bounds::from_corners(
5282 point(x_range.start, pixel_range.start),
5283 point(x_range.end, pixel_range.end),
5284 );
5285 quads.push(quad(
5286 bounds,
5287 Corners::default(),
5288 pixel_range.color,
5289 Edges::default(),
5290 Hsla::transparent_black(),
5291 ));
5292 }
5293
5294 quads
5295 }
5296}
5297
5298struct CreaseTrailerLayout {
5299 element: AnyElement,
5300 bounds: Bounds<Pixels>,
5301}
5302
5303struct PositionMap {
5304 size: Size<Pixels>,
5305 line_height: Pixels,
5306 scroll_pixel_position: gpui::Point<Pixels>,
5307 scroll_max: gpui::Point<f32>,
5308 em_width: Pixels,
5309 em_advance: Pixels,
5310 line_layouts: Vec<LineWithInvisibles>,
5311 snapshot: EditorSnapshot,
5312}
5313
5314#[derive(Debug, Copy, Clone)]
5315pub struct PointForPosition {
5316 pub previous_valid: DisplayPoint,
5317 pub next_valid: DisplayPoint,
5318 pub exact_unclipped: DisplayPoint,
5319 pub column_overshoot_after_line_end: u32,
5320}
5321
5322impl PointForPosition {
5323 pub fn as_valid(&self) -> Option<DisplayPoint> {
5324 if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped {
5325 Some(self.previous_valid)
5326 } else {
5327 None
5328 }
5329 }
5330}
5331
5332impl PositionMap {
5333 fn point_for_position(
5334 &self,
5335 text_bounds: Bounds<Pixels>,
5336 position: gpui::Point<Pixels>,
5337 ) -> PointForPosition {
5338 let scroll_position = self.snapshot.scroll_position();
5339 let position = position - text_bounds.origin;
5340 let y = position.y.max(px(0.)).min(self.size.height);
5341 let x = position.x + (scroll_position.x * self.em_width);
5342 let row = ((y / self.line_height) + scroll_position.y) as u32;
5343
5344 let (column, x_overshoot_after_line_end) = if let Some(line) = self
5345 .line_layouts
5346 .get(row as usize - scroll_position.y as usize)
5347 {
5348 if let Some(ix) = line.index_for_x(x) {
5349 (ix as u32, px(0.))
5350 } else {
5351 (line.len as u32, px(0.).max(x - line.width))
5352 }
5353 } else {
5354 (0, x)
5355 };
5356
5357 let mut exact_unclipped = DisplayPoint::new(DisplayRow(row), column);
5358 let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
5359 let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
5360
5361 let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32;
5362 *exact_unclipped.column_mut() += column_overshoot_after_line_end;
5363 PointForPosition {
5364 previous_valid,
5365 next_valid,
5366 exact_unclipped,
5367 column_overshoot_after_line_end,
5368 }
5369 }
5370}
5371
5372struct BlockLayout {
5373 row: DisplayRow,
5374 element: AnyElement,
5375 available_space: Size<AvailableSpace>,
5376 style: BlockStyle,
5377}
5378
5379fn layout_line(
5380 row: DisplayRow,
5381 snapshot: &EditorSnapshot,
5382 style: &EditorStyle,
5383 cx: &mut WindowContext,
5384) -> LineWithInvisibles {
5385 let chunks = snapshot.highlighted_chunks(row..row + DisplayRow(1), true, style);
5386 LineWithInvisibles::from_chunks(chunks, &style.text, MAX_LINE_LEN, 1, &[], snapshot.mode, cx)
5387 .pop()
5388 .unwrap()
5389}
5390
5391#[derive(Debug)]
5392pub struct IndentGuideLayout {
5393 origin: gpui::Point<Pixels>,
5394 length: Pixels,
5395 single_indent_width: Pixels,
5396 depth: u32,
5397 active: bool,
5398 settings: IndentGuideSettings,
5399}
5400
5401pub struct CursorLayout {
5402 origin: gpui::Point<Pixels>,
5403 block_width: Pixels,
5404 line_height: Pixels,
5405 color: Hsla,
5406 shape: CursorShape,
5407 block_text: Option<ShapedLine>,
5408 cursor_name: Option<AnyElement>,
5409}
5410
5411#[derive(Debug)]
5412pub struct CursorName {
5413 string: SharedString,
5414 color: Hsla,
5415 is_top_row: bool,
5416}
5417
5418impl CursorLayout {
5419 pub fn new(
5420 origin: gpui::Point<Pixels>,
5421 block_width: Pixels,
5422 line_height: Pixels,
5423 color: Hsla,
5424 shape: CursorShape,
5425 block_text: Option<ShapedLine>,
5426 ) -> CursorLayout {
5427 CursorLayout {
5428 origin,
5429 block_width,
5430 line_height,
5431 color,
5432 shape,
5433 block_text,
5434 cursor_name: None,
5435 }
5436 }
5437
5438 pub fn bounding_rect(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
5439 Bounds {
5440 origin: self.origin + origin,
5441 size: size(self.block_width, self.line_height),
5442 }
5443 }
5444
5445 fn bounds(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
5446 match self.shape {
5447 CursorShape::Bar => Bounds {
5448 origin: self.origin + origin,
5449 size: size(px(2.0), self.line_height),
5450 },
5451 CursorShape::Block | CursorShape::Hollow => Bounds {
5452 origin: self.origin + origin,
5453 size: size(self.block_width, self.line_height),
5454 },
5455 CursorShape::Underscore => Bounds {
5456 origin: self.origin
5457 + origin
5458 + gpui::Point::new(Pixels::ZERO, self.line_height - px(2.0)),
5459 size: size(self.block_width, px(2.0)),
5460 },
5461 }
5462 }
5463
5464 pub fn layout(
5465 &mut self,
5466 origin: gpui::Point<Pixels>,
5467 cursor_name: Option<CursorName>,
5468 cx: &mut WindowContext,
5469 ) {
5470 if let Some(cursor_name) = cursor_name {
5471 let bounds = self.bounds(origin);
5472 let text_size = self.line_height / 1.5;
5473
5474 let name_origin = if cursor_name.is_top_row {
5475 point(bounds.right() - px(1.), bounds.top())
5476 } else {
5477 point(bounds.left(), bounds.top() - text_size / 2. - px(1.))
5478 };
5479 let mut name_element = div()
5480 .bg(self.color)
5481 .text_size(text_size)
5482 .px_0p5()
5483 .line_height(text_size + px(2.))
5484 .text_color(cursor_name.color)
5485 .child(cursor_name.string.clone())
5486 .into_any_element();
5487
5488 name_element.prepaint_as_root(
5489 name_origin,
5490 size(AvailableSpace::MinContent, AvailableSpace::MinContent),
5491 cx,
5492 );
5493
5494 self.cursor_name = Some(name_element);
5495 }
5496 }
5497
5498 pub fn paint(&mut self, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
5499 let bounds = self.bounds(origin);
5500
5501 //Draw background or border quad
5502 let cursor = if matches!(self.shape, CursorShape::Hollow) {
5503 outline(bounds, self.color)
5504 } else {
5505 fill(bounds, self.color)
5506 };
5507
5508 if let Some(name) = &mut self.cursor_name {
5509 name.paint(cx);
5510 }
5511
5512 cx.paint_quad(cursor);
5513
5514 if let Some(block_text) = &self.block_text {
5515 block_text
5516 .paint(self.origin + origin, self.line_height, cx)
5517 .log_err();
5518 }
5519 }
5520
5521 pub fn shape(&self) -> CursorShape {
5522 self.shape
5523 }
5524}
5525
5526#[derive(Debug)]
5527pub struct HighlightedRange {
5528 pub start_y: Pixels,
5529 pub line_height: Pixels,
5530 pub lines: Vec<HighlightedRangeLine>,
5531 pub color: Hsla,
5532 pub corner_radius: Pixels,
5533}
5534
5535#[derive(Debug)]
5536pub struct HighlightedRangeLine {
5537 pub start_x: Pixels,
5538 pub end_x: Pixels,
5539}
5540
5541impl HighlightedRange {
5542 pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
5543 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
5544 self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
5545 self.paint_lines(
5546 self.start_y + self.line_height,
5547 &self.lines[1..],
5548 bounds,
5549 cx,
5550 );
5551 } else {
5552 self.paint_lines(self.start_y, &self.lines, bounds, cx);
5553 }
5554 }
5555
5556 fn paint_lines(
5557 &self,
5558 start_y: Pixels,
5559 lines: &[HighlightedRangeLine],
5560 _bounds: Bounds<Pixels>,
5561 cx: &mut WindowContext,
5562 ) {
5563 if lines.is_empty() {
5564 return;
5565 }
5566
5567 let first_line = lines.first().unwrap();
5568 let last_line = lines.last().unwrap();
5569
5570 let first_top_left = point(first_line.start_x, start_y);
5571 let first_top_right = point(first_line.end_x, start_y);
5572
5573 let curve_height = point(Pixels::ZERO, self.corner_radius);
5574 let curve_width = |start_x: Pixels, end_x: Pixels| {
5575 let max = (end_x - start_x) / 2.;
5576 let width = if max < self.corner_radius {
5577 max
5578 } else {
5579 self.corner_radius
5580 };
5581
5582 point(width, Pixels::ZERO)
5583 };
5584
5585 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
5586 let mut path = gpui::Path::new(first_top_right - top_curve_width);
5587 path.curve_to(first_top_right + curve_height, first_top_right);
5588
5589 let mut iter = lines.iter().enumerate().peekable();
5590 while let Some((ix, line)) = iter.next() {
5591 let bottom_right = point(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
5592
5593 if let Some((_, next_line)) = iter.peek() {
5594 let next_top_right = point(next_line.end_x, bottom_right.y);
5595
5596 match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
5597 Ordering::Equal => {
5598 path.line_to(bottom_right);
5599 }
5600 Ordering::Less => {
5601 let curve_width = curve_width(next_top_right.x, bottom_right.x);
5602 path.line_to(bottom_right - curve_height);
5603 if self.corner_radius > Pixels::ZERO {
5604 path.curve_to(bottom_right - curve_width, bottom_right);
5605 }
5606 path.line_to(next_top_right + curve_width);
5607 if self.corner_radius > Pixels::ZERO {
5608 path.curve_to(next_top_right + curve_height, next_top_right);
5609 }
5610 }
5611 Ordering::Greater => {
5612 let curve_width = curve_width(bottom_right.x, next_top_right.x);
5613 path.line_to(bottom_right - curve_height);
5614 if self.corner_radius > Pixels::ZERO {
5615 path.curve_to(bottom_right + curve_width, bottom_right);
5616 }
5617 path.line_to(next_top_right - curve_width);
5618 if self.corner_radius > Pixels::ZERO {
5619 path.curve_to(next_top_right + curve_height, next_top_right);
5620 }
5621 }
5622 }
5623 } else {
5624 let curve_width = curve_width(line.start_x, line.end_x);
5625 path.line_to(bottom_right - curve_height);
5626 if self.corner_radius > Pixels::ZERO {
5627 path.curve_to(bottom_right - curve_width, bottom_right);
5628 }
5629
5630 let bottom_left = point(line.start_x, bottom_right.y);
5631 path.line_to(bottom_left + curve_width);
5632 if self.corner_radius > Pixels::ZERO {
5633 path.curve_to(bottom_left - curve_height, bottom_left);
5634 }
5635 }
5636 }
5637
5638 if first_line.start_x > last_line.start_x {
5639 let curve_width = curve_width(last_line.start_x, first_line.start_x);
5640 let second_top_left = point(last_line.start_x, start_y + self.line_height);
5641 path.line_to(second_top_left + curve_height);
5642 if self.corner_radius > Pixels::ZERO {
5643 path.curve_to(second_top_left + curve_width, second_top_left);
5644 }
5645 let first_bottom_left = point(first_line.start_x, second_top_left.y);
5646 path.line_to(first_bottom_left - curve_width);
5647 if self.corner_radius > Pixels::ZERO {
5648 path.curve_to(first_bottom_left - curve_height, first_bottom_left);
5649 }
5650 }
5651
5652 path.line_to(first_top_left + curve_height);
5653 if self.corner_radius > Pixels::ZERO {
5654 path.curve_to(first_top_left + top_curve_width, first_top_left);
5655 }
5656 path.line_to(first_top_right - top_curve_width);
5657
5658 cx.paint_path(path, self.color);
5659 }
5660}
5661
5662pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
5663 (delta.pow(1.5) / 100.0).into()
5664}
5665
5666fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
5667 (delta.pow(1.2) / 300.0).into()
5668}
5669
5670#[cfg(test)]
5671mod tests {
5672 use super::*;
5673 use crate::{
5674 display_map::{BlockDisposition, BlockProperties},
5675 editor_tests::{init_test, update_test_language_settings},
5676 Editor, MultiBuffer,
5677 };
5678 use gpui::{TestAppContext, VisualTestContext};
5679 use language::language_settings;
5680 use log::info;
5681 use std::num::NonZeroU32;
5682 use ui::Context;
5683 use util::test::sample_text;
5684
5685 #[gpui::test]
5686 fn test_shape_line_numbers(cx: &mut TestAppContext) {
5687 init_test(cx, |_| {});
5688 let window = cx.add_window(|cx| {
5689 let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
5690 Editor::new(EditorMode::Full, buffer, None, true, cx)
5691 });
5692
5693 let editor = window.root(cx).unwrap();
5694 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
5695 let element = EditorElement::new(&editor, style);
5696 let snapshot = window.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
5697
5698 let layouts = cx
5699 .update_window(*window, |_, cx| {
5700 element.layout_line_numbers(
5701 DisplayRow(0)..DisplayRow(6),
5702 (0..6).map(MultiBufferRow).map(Some),
5703 &Default::default(),
5704 Some(DisplayPoint::new(DisplayRow(0), 0)),
5705 &snapshot,
5706 cx,
5707 )
5708 })
5709 .unwrap();
5710 assert_eq!(layouts.len(), 6);
5711
5712 let relative_rows = window
5713 .update(cx, |editor, cx| {
5714 let snapshot = editor.snapshot(cx);
5715 element.calculate_relative_line_numbers(
5716 &snapshot,
5717 &(DisplayRow(0)..DisplayRow(6)),
5718 Some(DisplayRow(3)),
5719 )
5720 })
5721 .unwrap();
5722 assert_eq!(relative_rows[&DisplayRow(0)], 3);
5723 assert_eq!(relative_rows[&DisplayRow(1)], 2);
5724 assert_eq!(relative_rows[&DisplayRow(2)], 1);
5725 // current line has no relative number
5726 assert_eq!(relative_rows[&DisplayRow(4)], 1);
5727 assert_eq!(relative_rows[&DisplayRow(5)], 2);
5728
5729 // works if cursor is before screen
5730 let relative_rows = window
5731 .update(cx, |editor, cx| {
5732 let snapshot = editor.snapshot(cx);
5733 element.calculate_relative_line_numbers(
5734 &snapshot,
5735 &(DisplayRow(3)..DisplayRow(6)),
5736 Some(DisplayRow(1)),
5737 )
5738 })
5739 .unwrap();
5740 assert_eq!(relative_rows.len(), 3);
5741 assert_eq!(relative_rows[&DisplayRow(3)], 2);
5742 assert_eq!(relative_rows[&DisplayRow(4)], 3);
5743 assert_eq!(relative_rows[&DisplayRow(5)], 4);
5744
5745 // works if cursor is after screen
5746 let relative_rows = window
5747 .update(cx, |editor, cx| {
5748 let snapshot = editor.snapshot(cx);
5749 element.calculate_relative_line_numbers(
5750 &snapshot,
5751 &(DisplayRow(0)..DisplayRow(3)),
5752 Some(DisplayRow(6)),
5753 )
5754 })
5755 .unwrap();
5756 assert_eq!(relative_rows.len(), 3);
5757 assert_eq!(relative_rows[&DisplayRow(0)], 5);
5758 assert_eq!(relative_rows[&DisplayRow(1)], 4);
5759 assert_eq!(relative_rows[&DisplayRow(2)], 3);
5760 }
5761
5762 #[gpui::test]
5763 async fn test_vim_visual_selections(cx: &mut TestAppContext) {
5764 init_test(cx, |_| {});
5765
5766 let window = cx.add_window(|cx| {
5767 let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
5768 Editor::new(EditorMode::Full, buffer, None, true, cx)
5769 });
5770 let cx = &mut VisualTestContext::from_window(*window, cx);
5771 let editor = window.root(cx).unwrap();
5772 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
5773
5774 window
5775 .update(cx, |editor, cx| {
5776 editor.cursor_shape = CursorShape::Block;
5777 editor.change_selections(None, cx, |s| {
5778 s.select_ranges([
5779 Point::new(0, 0)..Point::new(1, 0),
5780 Point::new(3, 2)..Point::new(3, 3),
5781 Point::new(5, 6)..Point::new(6, 0),
5782 ]);
5783 });
5784 })
5785 .unwrap();
5786
5787 let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
5788 EditorElement::new(&editor, style)
5789 });
5790
5791 assert_eq!(state.selections.len(), 1);
5792 let local_selections = &state.selections[0].1;
5793 assert_eq!(local_selections.len(), 3);
5794 // moves cursor back one line
5795 assert_eq!(
5796 local_selections[0].head,
5797 DisplayPoint::new(DisplayRow(0), 6)
5798 );
5799 assert_eq!(
5800 local_selections[0].range,
5801 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0)
5802 );
5803
5804 // moves cursor back one column
5805 assert_eq!(
5806 local_selections[1].range,
5807 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 3)
5808 );
5809 assert_eq!(
5810 local_selections[1].head,
5811 DisplayPoint::new(DisplayRow(3), 2)
5812 );
5813
5814 // leaves cursor on the max point
5815 assert_eq!(
5816 local_selections[2].range,
5817 DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(6), 0)
5818 );
5819 assert_eq!(
5820 local_selections[2].head,
5821 DisplayPoint::new(DisplayRow(6), 0)
5822 );
5823
5824 // active lines does not include 1 (even though the range of the selection does)
5825 assert_eq!(
5826 state.active_rows.keys().cloned().collect::<Vec<_>>(),
5827 vec![DisplayRow(0), DisplayRow(3), DisplayRow(5), DisplayRow(6)]
5828 );
5829
5830 // multi-buffer support
5831 // in DisplayPoint coordinates, this is what we're dealing with:
5832 // 0: [[file
5833 // 1: header
5834 // 2: section]]
5835 // 3: aaaaaa
5836 // 4: bbbbbb
5837 // 5: cccccc
5838 // 6:
5839 // 7: [[footer]]
5840 // 8: [[header]]
5841 // 9: ffffff
5842 // 10: gggggg
5843 // 11: hhhhhh
5844 // 12:
5845 // 13: [[footer]]
5846 // 14: [[file
5847 // 15: header
5848 // 16: section]]
5849 // 17: bbbbbb
5850 // 18: cccccc
5851 // 19: dddddd
5852 // 20: [[footer]]
5853 let window = cx.add_window(|cx| {
5854 let buffer = MultiBuffer::build_multi(
5855 [
5856 (
5857 &(sample_text(8, 6, 'a') + "\n"),
5858 vec![
5859 Point::new(0, 0)..Point::new(3, 0),
5860 Point::new(4, 0)..Point::new(7, 0),
5861 ],
5862 ),
5863 (
5864 &(sample_text(8, 6, 'a') + "\n"),
5865 vec![Point::new(1, 0)..Point::new(3, 0)],
5866 ),
5867 ],
5868 cx,
5869 );
5870 Editor::new(EditorMode::Full, buffer, None, true, cx)
5871 });
5872 let editor = window.root(cx).unwrap();
5873 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
5874 let _state = window.update(cx, |editor, cx| {
5875 editor.cursor_shape = CursorShape::Block;
5876 editor.change_selections(None, cx, |s| {
5877 s.select_display_ranges([
5878 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(7), 0),
5879 DisplayPoint::new(DisplayRow(10), 0)..DisplayPoint::new(DisplayRow(13), 0),
5880 ]);
5881 });
5882 });
5883
5884 let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
5885 EditorElement::new(&editor, style)
5886 });
5887 assert_eq!(state.selections.len(), 1);
5888 let local_selections = &state.selections[0].1;
5889 assert_eq!(local_selections.len(), 2);
5890
5891 // moves cursor on excerpt boundary back a line
5892 // and doesn't allow selection to bleed through
5893 assert_eq!(
5894 local_selections[0].range,
5895 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(7), 0)
5896 );
5897 assert_eq!(
5898 local_selections[0].head,
5899 DisplayPoint::new(DisplayRow(6), 0)
5900 );
5901 // moves cursor on buffer boundary back two lines
5902 // and doesn't allow selection to bleed through
5903 assert_eq!(
5904 local_selections[1].range,
5905 DisplayPoint::new(DisplayRow(10), 0)..DisplayPoint::new(DisplayRow(13), 0)
5906 );
5907 assert_eq!(
5908 local_selections[1].head,
5909 DisplayPoint::new(DisplayRow(12), 0)
5910 );
5911 }
5912
5913 #[gpui::test]
5914 fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
5915 init_test(cx, |_| {});
5916
5917 let window = cx.add_window(|cx| {
5918 let buffer = MultiBuffer::build_simple("", cx);
5919 Editor::new(EditorMode::Full, buffer, None, true, cx)
5920 });
5921 let cx = &mut VisualTestContext::from_window(*window, cx);
5922 let editor = window.root(cx).unwrap();
5923 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
5924 window
5925 .update(cx, |editor, cx| {
5926 editor.set_placeholder_text("hello", cx);
5927 editor.insert_blocks(
5928 [BlockProperties {
5929 style: BlockStyle::Fixed,
5930 disposition: BlockDisposition::Above,
5931 height: 3,
5932 position: Anchor::min(),
5933 render: Box::new(|_| div().into_any()),
5934 }],
5935 None,
5936 cx,
5937 );
5938
5939 // Blur the editor so that it displays placeholder text.
5940 cx.blur();
5941 })
5942 .unwrap();
5943
5944 let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
5945 EditorElement::new(&editor, style)
5946 });
5947 assert_eq!(state.position_map.line_layouts.len(), 4);
5948 assert_eq!(
5949 state
5950 .line_numbers
5951 .iter()
5952 .map(Option::is_some)
5953 .collect::<Vec<_>>(),
5954 &[false, false, false, true]
5955 );
5956 }
5957
5958 #[gpui::test]
5959 fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
5960 const TAB_SIZE: u32 = 4;
5961
5962 let input_text = "\t \t|\t| a b";
5963 let expected_invisibles = vec![
5964 Invisible::Tab {
5965 line_start_offset: 0,
5966 line_end_offset: TAB_SIZE as usize,
5967 },
5968 Invisible::Whitespace {
5969 line_offset: TAB_SIZE as usize,
5970 },
5971 Invisible::Tab {
5972 line_start_offset: TAB_SIZE as usize + 1,
5973 line_end_offset: TAB_SIZE as usize * 2,
5974 },
5975 Invisible::Tab {
5976 line_start_offset: TAB_SIZE as usize * 2 + 1,
5977 line_end_offset: TAB_SIZE as usize * 3,
5978 },
5979 Invisible::Whitespace {
5980 line_offset: TAB_SIZE as usize * 3 + 1,
5981 },
5982 Invisible::Whitespace {
5983 line_offset: TAB_SIZE as usize * 3 + 3,
5984 },
5985 ];
5986 assert_eq!(
5987 expected_invisibles.len(),
5988 input_text
5989 .chars()
5990 .filter(|initial_char| initial_char.is_whitespace())
5991 .count(),
5992 "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
5993 );
5994
5995 init_test(cx, |s| {
5996 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
5997 s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
5998 });
5999
6000 let actual_invisibles =
6001 collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, px(500.0));
6002
6003 assert_eq!(expected_invisibles, actual_invisibles);
6004 }
6005
6006 #[gpui::test]
6007 fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
6008 init_test(cx, |s| {
6009 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
6010 s.defaults.tab_size = NonZeroU32::new(4);
6011 });
6012
6013 for editor_mode_without_invisibles in [
6014 EditorMode::SingleLine,
6015 EditorMode::AutoHeight { max_lines: 100 },
6016 ] {
6017 let invisibles = collect_invisibles_from_new_editor(
6018 cx,
6019 editor_mode_without_invisibles,
6020 "\t\t\t| | a b",
6021 px(500.0),
6022 );
6023 assert!(invisibles.is_empty(),
6024 "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}");
6025 }
6026 }
6027
6028 #[gpui::test]
6029 fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
6030 let tab_size = 4;
6031 let input_text = "a\tbcd ".repeat(9);
6032 let repeated_invisibles = [
6033 Invisible::Tab {
6034 line_start_offset: 1,
6035 line_end_offset: tab_size as usize,
6036 },
6037 Invisible::Whitespace {
6038 line_offset: tab_size as usize + 3,
6039 },
6040 Invisible::Whitespace {
6041 line_offset: tab_size as usize + 4,
6042 },
6043 Invisible::Whitespace {
6044 line_offset: tab_size as usize + 5,
6045 },
6046 Invisible::Whitespace {
6047 line_offset: tab_size as usize + 6,
6048 },
6049 Invisible::Whitespace {
6050 line_offset: tab_size as usize + 7,
6051 },
6052 ];
6053 let expected_invisibles = std::iter::once(repeated_invisibles)
6054 .cycle()
6055 .take(9)
6056 .flatten()
6057 .collect::<Vec<_>>();
6058 assert_eq!(
6059 expected_invisibles.len(),
6060 input_text
6061 .chars()
6062 .filter(|initial_char| initial_char.is_whitespace())
6063 .count(),
6064 "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
6065 );
6066 info!("Expected invisibles: {expected_invisibles:?}");
6067
6068 init_test(cx, |_| {});
6069
6070 // Put the same string with repeating whitespace pattern into editors of various size,
6071 // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
6072 let resize_step = 10.0;
6073 let mut editor_width = 200.0;
6074 while editor_width <= 1000.0 {
6075 update_test_language_settings(cx, |s| {
6076 s.defaults.tab_size = NonZeroU32::new(tab_size);
6077 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
6078 s.defaults.preferred_line_length = Some(editor_width as u32);
6079 s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
6080 });
6081
6082 let actual_invisibles = collect_invisibles_from_new_editor(
6083 cx,
6084 EditorMode::Full,
6085 &input_text,
6086 px(editor_width),
6087 );
6088
6089 // Whatever the editor size is, ensure it has the same invisible kinds in the same order
6090 // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
6091 let mut i = 0;
6092 for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
6093 i = actual_index;
6094 match expected_invisibles.get(i) {
6095 Some(expected_invisible) => match (expected_invisible, actual_invisible) {
6096 (Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
6097 | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
6098 _ => {
6099 panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}")
6100 }
6101 },
6102 None => panic!("Unexpected extra invisible {actual_invisible:?} at index {i}"),
6103 }
6104 }
6105 let missing_expected_invisibles = &expected_invisibles[i + 1..];
6106 assert!(
6107 missing_expected_invisibles.is_empty(),
6108 "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
6109 );
6110
6111 editor_width += resize_step;
6112 }
6113 }
6114
6115 fn collect_invisibles_from_new_editor(
6116 cx: &mut TestAppContext,
6117 editor_mode: EditorMode,
6118 input_text: &str,
6119 editor_width: Pixels,
6120 ) -> Vec<Invisible> {
6121 info!(
6122 "Creating editor with mode {editor_mode:?}, width {}px and text '{input_text}'",
6123 editor_width.0
6124 );
6125 let window = cx.add_window(|cx| {
6126 let buffer = MultiBuffer::build_simple(&input_text, cx);
6127 Editor::new(editor_mode, buffer, None, true, cx)
6128 });
6129 let cx = &mut VisualTestContext::from_window(*window, cx);
6130 let editor = window.root(cx).unwrap();
6131 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
6132 window
6133 .update(cx, |editor, cx| {
6134 editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
6135 editor.set_wrap_width(Some(editor_width), cx);
6136 })
6137 .unwrap();
6138 let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
6139 EditorElement::new(&editor, style)
6140 });
6141 state
6142 .position_map
6143 .line_layouts
6144 .iter()
6145 .flat_map(|line_with_invisibles| &line_with_invisibles.invisibles)
6146 .cloned()
6147 .collect()
6148 }
6149}
6150
6151pub fn register_action<T: Action>(
6152 view: &View<Editor>,
6153 cx: &mut WindowContext,
6154 listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
6155) {
6156 let view = view.clone();
6157 cx.on_action(TypeId::of::<T>(), move |action, phase, cx| {
6158 let action = action.downcast_ref().unwrap();
6159 if phase == DispatchPhase::Bubble {
6160 view.update(cx, |editor, cx| {
6161 listener(editor, action, cx);
6162 })
6163 }
6164 })
6165}
6166
6167fn compute_auto_height_layout(
6168 editor: &mut Editor,
6169 max_lines: usize,
6170 max_line_number_width: Pixels,
6171 known_dimensions: Size<Option<Pixels>>,
6172 available_width: AvailableSpace,
6173 cx: &mut ViewContext<Editor>,
6174) -> Option<Size<Pixels>> {
6175 let width = known_dimensions.width.or_else(|| {
6176 if let AvailableSpace::Definite(available_width) = available_width {
6177 Some(available_width)
6178 } else {
6179 None
6180 }
6181 })?;
6182 if let Some(height) = known_dimensions.height {
6183 return Some(size(width, height));
6184 }
6185
6186 let style = editor.style.as_ref().unwrap();
6187 let font_id = cx.text_system().resolve_font(&style.text.font());
6188 let font_size = style.text.font_size.to_pixels(cx.rem_size());
6189 let line_height = style.text.line_height_in_pixels(cx.rem_size());
6190 let em_width = cx
6191 .text_system()
6192 .typographic_bounds(font_id, font_size, 'm')
6193 .unwrap()
6194 .size
6195 .width;
6196
6197 let mut snapshot = editor.snapshot(cx);
6198 let gutter_dimensions =
6199 snapshot.gutter_dimensions(font_id, font_size, em_width, max_line_number_width, cx);
6200
6201 editor.gutter_dimensions = gutter_dimensions;
6202 let text_width = width - gutter_dimensions.width;
6203 let overscroll = size(em_width, px(0.));
6204
6205 let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width;
6206 if editor.set_wrap_width(Some(editor_width), cx) {
6207 snapshot = editor.snapshot(cx);
6208 }
6209
6210 let scroll_height = Pixels::from(snapshot.max_point().row().next_row().0) * line_height;
6211 let height = scroll_height
6212 .max(line_height)
6213 .min(line_height * max_lines as f32);
6214
6215 Some(size(width, height))
6216}