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