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