1use crate::{
2 BlockId, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR, ChunkRendererContext,
3 ChunkReplacement, ContextMenuPlacement, CursorShape, CustomBlockId, DisplayDiffHunk,
4 DisplayPoint, DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode,
5 Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, FILE_HEADER_HEIGHT,
6 FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
7 InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineHighlight, LineUp,
8 MAX_LINE_LEN, MIN_LINE_NUMBER_DIGITS, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts,
9 PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection,
10 SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold,
11 code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
12 display_map::{
13 Block, BlockContext, BlockStyle, DisplaySnapshot, FoldId, HighlightedChunk, ToDisplayPoint,
14 },
15 editor_settings::{
16 CurrentLineHighlight, DoubleClickInMultibuffer, MultiCursorModifier, ScrollBeyondLastLine,
17 ScrollbarAxes, ScrollbarDiagnostics, ShowScrollbar,
18 },
19 git::blame::{BlameRenderer, GitBlame, GlobalBlameRenderer},
20 hover_popover::{
21 self, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, hover_at,
22 },
23 inlay_hint_settings,
24 items::BufferSearchHighlights,
25 mouse_context_menu::{self, MenuPosition},
26 scroll::scroll_amount::ScrollAmount,
27};
28use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
29use client::ParticipantIndex;
30use collections::{BTreeMap, HashMap};
31use feature_flags::{Debugger, FeatureFlagAppExt};
32use file_icons::FileIcons;
33use git::{Oid, blame::BlameEntry, status::FileStatus};
34use gpui::{
35 Action, Along, AnyElement, App, AvailableSpace, Axis as ScrollbarAxis, BorderStyle, Bounds,
36 ClickEvent, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase, Edges, Element,
37 ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox, Hsla,
38 InteractiveElement, IntoElement, Keystroke, Length, ModifiersChangedEvent, MouseButton,
39 MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta,
40 ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
41 TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill,
42 linear_color_stop, linear_gradient, outline, point, px, quad, relative, size, solid_background,
43 transparent_black,
44};
45use itertools::Itertools;
46use language::language_settings::{
47 IndentGuideBackgroundColoring, IndentGuideColoring, IndentGuideSettings, ShowWhitespaceSetting,
48};
49use lsp::DiagnosticSeverity;
50use multi_buffer::{
51 Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint,
52 MultiBufferRow, RowInfo,
53};
54use project::{
55 debugger::breakpoint_store::Breakpoint,
56 project_settings::{self, GitGutterSetting, GitHunkStyleSetting, ProjectSettings},
57};
58use settings::Settings;
59use smallvec::{SmallVec, smallvec};
60use std::{
61 any::TypeId,
62 borrow::Cow,
63 cmp::{self, Ordering},
64 fmt::{self, Write},
65 iter, mem,
66 ops::{Deref, Range},
67 rc::Rc,
68 sync::Arc,
69 time::Duration,
70};
71use sum_tree::Bias;
72use text::BufferId;
73use theme::{ActiveTheme, Appearance, BufferLineHeight, PlayerColor};
74use ui::{ButtonLike, KeyBinding, POPOVER_Y_PADDING, Tooltip, h_flex, prelude::*};
75use unicode_segmentation::UnicodeSegmentation;
76use util::{RangeExt, ResultExt, debug_panic};
77use workspace::{Workspace, item::Item, notifications::NotifyTaskExt};
78
79const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
80
81/// Determines what kinds of highlights should be applied to a lines background.
82#[derive(Clone, Copy, Default)]
83struct LineHighlightSpec {
84 selection: bool,
85 breakpoint: bool,
86 _active_stack_frame: bool,
87}
88
89struct SelectionLayout {
90 head: DisplayPoint,
91 cursor_shape: CursorShape,
92 is_newest: bool,
93 is_local: bool,
94 range: Range<DisplayPoint>,
95 active_rows: Range<DisplayRow>,
96 user_name: Option<SharedString>,
97}
98
99impl SelectionLayout {
100 fn new<T: ToPoint + ToDisplayPoint + Clone>(
101 selection: Selection<T>,
102 line_mode: bool,
103 cursor_shape: CursorShape,
104 map: &DisplaySnapshot,
105 is_newest: bool,
106 is_local: bool,
107 user_name: Option<SharedString>,
108 ) -> Self {
109 let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
110 let display_selection = point_selection.map(|p| p.to_display_point(map));
111 let mut range = display_selection.range();
112 let mut head = display_selection.head();
113 let mut active_rows = map.prev_line_boundary(point_selection.start).1.row()
114 ..map.next_line_boundary(point_selection.end).1.row();
115
116 // vim visual line mode
117 if line_mode {
118 let point_range = map.expand_to_line(point_selection.range());
119 range = point_range.start.to_display_point(map)..point_range.end.to_display_point(map);
120 }
121
122 // any vim visual mode (including line mode)
123 if (cursor_shape == CursorShape::Block || cursor_shape == CursorShape::Hollow)
124 && !range.is_empty()
125 && !selection.reversed
126 {
127 if head.column() > 0 {
128 head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left)
129 } else if head.row().0 > 0 && head != map.max_point() {
130 head = map.clip_point(
131 DisplayPoint::new(
132 head.row().previous_row(),
133 map.line_len(head.row().previous_row()),
134 ),
135 Bias::Left,
136 );
137 // updating range.end is a no-op unless you're cursor is
138 // on the newline containing a multi-buffer divider
139 // in which case the clip_point may have moved the head up
140 // an additional row.
141 range.end = DisplayPoint::new(head.row().next_row(), 0);
142 active_rows.end = head.row();
143 }
144 }
145
146 Self {
147 head,
148 cursor_shape,
149 is_newest,
150 is_local,
151 range,
152 active_rows,
153 user_name,
154 }
155 }
156}
157
158pub struct EditorElement {
159 editor: Entity<Editor>,
160 style: EditorStyle,
161}
162
163type DisplayRowDelta = u32;
164
165impl EditorElement {
166 pub(crate) const SCROLLBAR_WIDTH: Pixels = px(15.);
167
168 pub fn new(editor: &Entity<Editor>, style: EditorStyle) -> Self {
169 Self {
170 editor: editor.clone(),
171 style,
172 }
173 }
174
175 fn register_actions(&self, window: &mut Window, cx: &mut App) {
176 let editor = &self.editor;
177 editor.update(cx, |editor, cx| {
178 for action in editor.editor_actions.borrow().values() {
179 (action)(window, cx)
180 }
181 });
182
183 crate::rust_analyzer_ext::apply_related_actions(editor, window, cx);
184 crate::clangd_ext::apply_related_actions(editor, window, cx);
185
186 register_action(editor, window, Editor::open_context_menu);
187 register_action(editor, window, Editor::move_left);
188 register_action(editor, window, Editor::move_right);
189 register_action(editor, window, Editor::move_down);
190 register_action(editor, window, Editor::move_down_by_lines);
191 register_action(editor, window, Editor::select_down_by_lines);
192 register_action(editor, window, Editor::move_up);
193 register_action(editor, window, Editor::move_up_by_lines);
194 register_action(editor, window, Editor::select_up_by_lines);
195 register_action(editor, window, Editor::select_page_down);
196 register_action(editor, window, Editor::select_page_up);
197 register_action(editor, window, Editor::cancel);
198 register_action(editor, window, Editor::newline);
199 register_action(editor, window, Editor::newline_above);
200 register_action(editor, window, Editor::newline_below);
201 register_action(editor, window, Editor::backspace);
202 register_action(editor, window, Editor::delete);
203 register_action(editor, window, Editor::tab);
204 register_action(editor, window, Editor::backtab);
205 register_action(editor, window, Editor::indent);
206 register_action(editor, window, Editor::outdent);
207 register_action(editor, window, Editor::autoindent);
208 register_action(editor, window, Editor::delete_line);
209 register_action(editor, window, Editor::join_lines);
210 register_action(editor, window, Editor::sort_lines_case_sensitive);
211 register_action(editor, window, Editor::sort_lines_case_insensitive);
212 register_action(editor, window, Editor::reverse_lines);
213 register_action(editor, window, Editor::shuffle_lines);
214 register_action(editor, window, Editor::toggle_case);
215 register_action(editor, window, Editor::convert_to_upper_case);
216 register_action(editor, window, Editor::convert_to_lower_case);
217 register_action(editor, window, Editor::convert_to_title_case);
218 register_action(editor, window, Editor::convert_to_snake_case);
219 register_action(editor, window, Editor::convert_to_kebab_case);
220 register_action(editor, window, Editor::convert_to_upper_camel_case);
221 register_action(editor, window, Editor::convert_to_lower_camel_case);
222 register_action(editor, window, Editor::convert_to_opposite_case);
223 register_action(editor, window, Editor::convert_to_rot13);
224 register_action(editor, window, Editor::convert_to_rot47);
225 register_action(editor, window, Editor::delete_to_previous_word_start);
226 register_action(editor, window, Editor::delete_to_previous_subword_start);
227 register_action(editor, window, Editor::delete_to_next_word_end);
228 register_action(editor, window, Editor::delete_to_next_subword_end);
229 register_action(editor, window, Editor::delete_to_beginning_of_line);
230 register_action(editor, window, Editor::delete_to_end_of_line);
231 register_action(editor, window, Editor::cut_to_end_of_line);
232 register_action(editor, window, Editor::duplicate_line_up);
233 register_action(editor, window, Editor::duplicate_line_down);
234 register_action(editor, window, Editor::duplicate_selection);
235 register_action(editor, window, Editor::move_line_up);
236 register_action(editor, window, Editor::move_line_down);
237 register_action(editor, window, Editor::transpose);
238 register_action(editor, window, Editor::rewrap);
239 register_action(editor, window, Editor::cut);
240 register_action(editor, window, Editor::kill_ring_cut);
241 register_action(editor, window, Editor::kill_ring_yank);
242 register_action(editor, window, Editor::copy);
243 register_action(editor, window, Editor::copy_and_trim);
244 register_action(editor, window, Editor::paste);
245 register_action(editor, window, Editor::undo);
246 register_action(editor, window, Editor::redo);
247 register_action(editor, window, Editor::move_page_up);
248 register_action(editor, window, Editor::move_page_down);
249 register_action(editor, window, Editor::next_screen);
250 register_action(editor, window, Editor::scroll_cursor_top);
251 register_action(editor, window, Editor::scroll_cursor_center);
252 register_action(editor, window, Editor::scroll_cursor_bottom);
253 register_action(editor, window, Editor::scroll_cursor_center_top_bottom);
254 register_action(editor, window, |editor, _: &LineDown, window, cx| {
255 editor.scroll_screen(&ScrollAmount::Line(1.), window, cx)
256 });
257 register_action(editor, window, |editor, _: &LineUp, window, cx| {
258 editor.scroll_screen(&ScrollAmount::Line(-1.), window, cx)
259 });
260 register_action(editor, window, |editor, _: &HalfPageDown, window, cx| {
261 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx)
262 });
263 register_action(
264 editor,
265 window,
266 |editor, HandleInput(text): &HandleInput, window, cx| {
267 if text.is_empty() {
268 return;
269 }
270 editor.handle_input(text, window, cx);
271 },
272 );
273 register_action(editor, window, |editor, _: &HalfPageUp, window, cx| {
274 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx)
275 });
276 register_action(editor, window, |editor, _: &PageDown, window, cx| {
277 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx)
278 });
279 register_action(editor, window, |editor, _: &PageUp, window, cx| {
280 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx)
281 });
282 register_action(editor, window, Editor::move_to_previous_word_start);
283 register_action(editor, window, Editor::move_to_previous_subword_start);
284 register_action(editor, window, Editor::move_to_next_word_end);
285 register_action(editor, window, Editor::move_to_next_subword_end);
286 register_action(editor, window, Editor::move_to_beginning_of_line);
287 register_action(editor, window, Editor::move_to_end_of_line);
288 register_action(editor, window, Editor::move_to_start_of_paragraph);
289 register_action(editor, window, Editor::move_to_end_of_paragraph);
290 register_action(editor, window, Editor::move_to_beginning);
291 register_action(editor, window, Editor::move_to_end);
292 register_action(editor, window, Editor::move_to_start_of_excerpt);
293 register_action(editor, window, Editor::move_to_start_of_next_excerpt);
294 register_action(editor, window, Editor::move_to_end_of_excerpt);
295 register_action(editor, window, Editor::move_to_end_of_previous_excerpt);
296 register_action(editor, window, Editor::select_up);
297 register_action(editor, window, Editor::select_down);
298 register_action(editor, window, Editor::select_left);
299 register_action(editor, window, Editor::select_right);
300 register_action(editor, window, Editor::select_to_previous_word_start);
301 register_action(editor, window, Editor::select_to_previous_subword_start);
302 register_action(editor, window, Editor::select_to_next_word_end);
303 register_action(editor, window, Editor::select_to_next_subword_end);
304 register_action(editor, window, Editor::select_to_beginning_of_line);
305 register_action(editor, window, Editor::select_to_end_of_line);
306 register_action(editor, window, Editor::select_to_start_of_paragraph);
307 register_action(editor, window, Editor::select_to_end_of_paragraph);
308 register_action(editor, window, Editor::select_to_start_of_excerpt);
309 register_action(editor, window, Editor::select_to_start_of_next_excerpt);
310 register_action(editor, window, Editor::select_to_end_of_excerpt);
311 register_action(editor, window, Editor::select_to_end_of_previous_excerpt);
312 register_action(editor, window, Editor::select_to_beginning);
313 register_action(editor, window, Editor::select_to_end);
314 register_action(editor, window, Editor::select_all);
315 register_action(editor, window, |editor, action, window, cx| {
316 editor.select_all_matches(action, window, cx).log_err();
317 });
318 register_action(editor, window, Editor::select_line);
319 register_action(editor, window, Editor::split_selection_into_lines);
320 register_action(editor, window, Editor::add_selection_above);
321 register_action(editor, window, Editor::add_selection_below);
322 register_action(editor, window, |editor, action, window, cx| {
323 editor.select_next(action, window, cx).log_err();
324 });
325 register_action(editor, window, |editor, action, window, cx| {
326 editor.select_previous(action, window, cx).log_err();
327 });
328 register_action(editor, window, Editor::toggle_comments);
329 register_action(editor, window, Editor::select_larger_syntax_node);
330 register_action(editor, window, Editor::select_smaller_syntax_node);
331 register_action(editor, window, Editor::select_enclosing_symbol);
332 register_action(editor, window, Editor::move_to_enclosing_bracket);
333 register_action(editor, window, Editor::undo_selection);
334 register_action(editor, window, Editor::redo_selection);
335 if !editor.read(cx).is_singleton(cx) {
336 register_action(editor, window, Editor::expand_excerpts);
337 register_action(editor, window, Editor::expand_excerpts_up);
338 register_action(editor, window, Editor::expand_excerpts_down);
339 }
340 register_action(editor, window, Editor::go_to_diagnostic);
341 register_action(editor, window, Editor::go_to_prev_diagnostic);
342 register_action(editor, window, Editor::go_to_next_hunk);
343 register_action(editor, window, Editor::go_to_prev_hunk);
344 register_action(editor, window, |editor, action, window, cx| {
345 editor
346 .go_to_definition(action, window, cx)
347 .detach_and_log_err(cx);
348 });
349 register_action(editor, window, |editor, action, window, cx| {
350 editor
351 .go_to_definition_split(action, window, cx)
352 .detach_and_log_err(cx);
353 });
354 register_action(editor, window, |editor, action, window, cx| {
355 editor
356 .go_to_declaration(action, window, cx)
357 .detach_and_log_err(cx);
358 });
359 register_action(editor, window, |editor, action, window, cx| {
360 editor
361 .go_to_declaration_split(action, window, cx)
362 .detach_and_log_err(cx);
363 });
364 register_action(editor, window, |editor, action, window, cx| {
365 editor
366 .go_to_implementation(action, window, cx)
367 .detach_and_log_err(cx);
368 });
369 register_action(editor, window, |editor, action, window, cx| {
370 editor
371 .go_to_implementation_split(action, window, cx)
372 .detach_and_log_err(cx);
373 });
374 register_action(editor, window, |editor, action, window, cx| {
375 editor
376 .go_to_type_definition(action, window, cx)
377 .detach_and_log_err(cx);
378 });
379 register_action(editor, window, |editor, action, window, cx| {
380 editor
381 .go_to_type_definition_split(action, window, cx)
382 .detach_and_log_err(cx);
383 });
384 register_action(editor, window, Editor::open_url);
385 register_action(editor, window, Editor::open_selected_filename);
386 register_action(editor, window, Editor::fold);
387 register_action(editor, window, Editor::fold_at_level);
388 register_action(editor, window, Editor::fold_all);
389 register_action(editor, window, Editor::fold_function_bodies);
390 register_action(editor, window, Editor::fold_recursive);
391 register_action(editor, window, Editor::toggle_fold);
392 register_action(editor, window, Editor::toggle_fold_recursive);
393 register_action(editor, window, Editor::unfold_lines);
394 register_action(editor, window, Editor::unfold_recursive);
395 register_action(editor, window, Editor::unfold_all);
396 register_action(editor, window, Editor::fold_selected_ranges);
397 register_action(editor, window, Editor::set_mark);
398 register_action(editor, window, Editor::swap_selection_ends);
399 register_action(editor, window, Editor::show_completions);
400 register_action(editor, window, Editor::show_word_completions);
401 register_action(editor, window, Editor::toggle_code_actions);
402 register_action(editor, window, Editor::open_excerpts);
403 register_action(editor, window, Editor::open_excerpts_in_split);
404 register_action(editor, window, Editor::open_proposed_changes_editor);
405 register_action(editor, window, Editor::toggle_soft_wrap);
406 register_action(editor, window, Editor::toggle_tab_bar);
407 register_action(editor, window, Editor::toggle_line_numbers);
408 register_action(editor, window, Editor::toggle_relative_line_numbers);
409 register_action(editor, window, Editor::toggle_indent_guides);
410 register_action(editor, window, Editor::toggle_inlay_hints);
411 register_action(editor, window, Editor::toggle_edit_predictions);
412 register_action(editor, window, Editor::toggle_inline_diagnostics);
413 register_action(editor, window, hover_popover::hover);
414 register_action(editor, window, Editor::reveal_in_finder);
415 register_action(editor, window, Editor::copy_path);
416 register_action(editor, window, Editor::copy_relative_path);
417 register_action(editor, window, Editor::copy_file_name);
418 register_action(editor, window, Editor::copy_file_name_without_extension);
419 register_action(editor, window, Editor::copy_highlight_json);
420 register_action(editor, window, Editor::copy_permalink_to_line);
421 register_action(editor, window, Editor::open_permalink_to_line);
422 register_action(editor, window, Editor::copy_file_location);
423 register_action(editor, window, Editor::toggle_git_blame);
424 register_action(editor, window, Editor::toggle_git_blame_inline);
425 register_action(editor, window, Editor::open_git_blame_commit);
426 register_action(editor, window, Editor::toggle_selected_diff_hunks);
427 register_action(editor, window, Editor::toggle_staged_selected_diff_hunks);
428 register_action(editor, window, Editor::stage_and_next);
429 register_action(editor, window, Editor::unstage_and_next);
430 register_action(editor, window, Editor::expand_all_diff_hunks);
431
432 register_action(editor, window, |editor, action, window, cx| {
433 if let Some(task) = editor.format(action, window, cx) {
434 task.detach_and_notify_err(window, cx);
435 } else {
436 cx.propagate();
437 }
438 });
439 register_action(editor, window, |editor, action, window, cx| {
440 if let Some(task) = editor.format_selections(action, window, cx) {
441 task.detach_and_notify_err(window, cx);
442 } else {
443 cx.propagate();
444 }
445 });
446 register_action(editor, window, |editor, action, window, cx| {
447 if let Some(task) = editor.organize_imports(action, window, cx) {
448 task.detach_and_notify_err(window, cx);
449 } else {
450 cx.propagate();
451 }
452 });
453 register_action(editor, window, Editor::restart_language_server);
454 register_action(editor, window, Editor::stop_language_server);
455 register_action(editor, window, Editor::show_character_palette);
456 register_action(editor, window, |editor, action, window, cx| {
457 if let Some(task) = editor.confirm_completion(action, window, cx) {
458 task.detach_and_notify_err(window, cx);
459 } else {
460 cx.propagate();
461 }
462 });
463 register_action(editor, window, |editor, action, window, cx| {
464 if let Some(task) = editor.confirm_completion_replace(action, window, cx) {
465 task.detach_and_notify_err(window, cx);
466 } else {
467 cx.propagate();
468 }
469 });
470 register_action(editor, window, |editor, action, window, cx| {
471 if let Some(task) = editor.confirm_completion_insert(action, window, cx) {
472 task.detach_and_notify_err(window, cx);
473 } else {
474 cx.propagate();
475 }
476 });
477 register_action(editor, window, |editor, action, window, cx| {
478 if let Some(task) = editor.compose_completion(action, window, cx) {
479 task.detach_and_notify_err(window, cx);
480 } else {
481 cx.propagate();
482 }
483 });
484 register_action(editor, window, |editor, action, window, cx| {
485 if let Some(task) = editor.confirm_code_action(action, window, cx) {
486 task.detach_and_notify_err(window, cx);
487 } else {
488 cx.propagate();
489 }
490 });
491 register_action(editor, window, |editor, action, window, cx| {
492 if let Some(task) = editor.rename(action, window, cx) {
493 task.detach_and_notify_err(window, cx);
494 } else {
495 cx.propagate();
496 }
497 });
498 register_action(editor, window, |editor, action, window, cx| {
499 if let Some(task) = editor.confirm_rename(action, window, cx) {
500 task.detach_and_notify_err(window, cx);
501 } else {
502 cx.propagate();
503 }
504 });
505 register_action(editor, window, |editor, action, window, cx| {
506 if let Some(task) = editor.find_all_references(action, window, cx) {
507 task.detach_and_log_err(cx);
508 } else {
509 cx.propagate();
510 }
511 });
512 register_action(editor, window, Editor::show_signature_help);
513 register_action(editor, window, Editor::next_edit_prediction);
514 register_action(editor, window, Editor::previous_edit_prediction);
515 register_action(editor, window, Editor::show_inline_completion);
516 register_action(editor, window, Editor::context_menu_first);
517 register_action(editor, window, Editor::context_menu_prev);
518 register_action(editor, window, Editor::context_menu_next);
519 register_action(editor, window, Editor::context_menu_last);
520 register_action(editor, window, Editor::display_cursor_names);
521 register_action(editor, window, Editor::unique_lines_case_insensitive);
522 register_action(editor, window, Editor::unique_lines_case_sensitive);
523 register_action(editor, window, Editor::accept_partial_inline_completion);
524 register_action(editor, window, Editor::accept_edit_prediction);
525 register_action(editor, window, Editor::restore_file);
526 register_action(editor, window, Editor::git_restore);
527 register_action(editor, window, Editor::apply_all_diff_hunks);
528 register_action(editor, window, Editor::apply_selected_diff_hunks);
529 register_action(editor, window, Editor::open_active_item_in_terminal);
530 register_action(editor, window, Editor::reload_file);
531 register_action(editor, window, Editor::spawn_nearest_task);
532 register_action(editor, window, Editor::insert_uuid_v4);
533 register_action(editor, window, Editor::insert_uuid_v7);
534 register_action(editor, window, Editor::open_selections_in_multibuffer);
535 if cx.has_flag::<Debugger>() {
536 register_action(editor, window, Editor::toggle_breakpoint);
537 register_action(editor, window, Editor::edit_log_breakpoint);
538 register_action(editor, window, Editor::enable_breakpoint);
539 register_action(editor, window, Editor::disable_breakpoint);
540 }
541 }
542
543 fn register_key_listeners(&self, window: &mut Window, _: &mut App, layout: &EditorLayout) {
544 let position_map = layout.position_map.clone();
545 window.on_key_event({
546 let editor = self.editor.clone();
547 move |event: &ModifiersChangedEvent, phase, window, cx| {
548 if phase != DispatchPhase::Bubble {
549 return;
550 }
551 editor.update(cx, |editor, cx| {
552 let inlay_hint_settings = inlay_hint_settings(
553 editor.selections.newest_anchor().head(),
554 &editor.buffer.read(cx).snapshot(cx),
555 cx,
556 );
557
558 if let Some(inlay_modifiers) = inlay_hint_settings
559 .toggle_on_modifiers_press
560 .as_ref()
561 .filter(|modifiers| modifiers.modified())
562 {
563 editor.refresh_inlay_hints(
564 InlayHintRefreshReason::ModifiersChanged(
565 inlay_modifiers == &event.modifiers,
566 ),
567 cx,
568 );
569 }
570
571 if editor.hover_state.focused(window, cx) {
572 return;
573 }
574
575 editor.handle_modifiers_changed(event.modifiers, &position_map, window, cx);
576 })
577 }
578 });
579 }
580
581 fn mouse_left_down(
582 editor: &mut Editor,
583 event: &MouseDownEvent,
584 hovered_hunk: Option<Range<Anchor>>,
585 position_map: &PositionMap,
586 line_numbers: &HashMap<MultiBufferRow, LineNumberLayout>,
587 window: &mut Window,
588 cx: &mut Context<Editor>,
589 ) {
590 if window.default_prevented() {
591 return;
592 }
593
594 let text_hitbox = &position_map.text_hitbox;
595 let gutter_hitbox = &position_map.gutter_hitbox;
596 let mut click_count = event.click_count;
597 let mut modifiers = event.modifiers;
598
599 if let Some(hovered_hunk) = hovered_hunk {
600 editor.toggle_single_diff_hunk(hovered_hunk, cx);
601 cx.notify();
602 return;
603 } else if gutter_hitbox.is_hovered(window) {
604 click_count = 3; // Simulate triple-click when clicking the gutter to select lines
605 } else if !text_hitbox.is_hovered(window) {
606 return;
607 }
608
609 let is_singleton = editor.buffer().read(cx).is_singleton();
610
611 if click_count == 2 && !is_singleton {
612 match EditorSettings::get_global(cx).double_click_in_multibuffer {
613 DoubleClickInMultibuffer::Select => {
614 // do nothing special on double click, all selection logic is below
615 }
616 DoubleClickInMultibuffer::Open => {
617 if modifiers.alt {
618 // if double click is made with alt, pretend it's a regular double click without opening and alt,
619 // and run the selection logic.
620 modifiers.alt = false;
621 } else {
622 let scroll_position_row =
623 position_map.scroll_pixel_position.y / position_map.line_height;
624 let display_row = (((event.position - gutter_hitbox.bounds.origin).y
625 + position_map.scroll_pixel_position.y)
626 / position_map.line_height)
627 as u32;
628 let multi_buffer_row = position_map
629 .snapshot
630 .display_point_to_point(
631 DisplayPoint::new(DisplayRow(display_row), 0),
632 Bias::Right,
633 )
634 .row;
635 let line_offset_from_top = display_row - scroll_position_row as u32;
636 // if double click is made without alt, open the corresponding excerp
637 editor.open_excerpts_common(
638 Some(JumpData::MultiBufferRow {
639 row: MultiBufferRow(multi_buffer_row),
640 line_offset_from_top,
641 }),
642 false,
643 window,
644 cx,
645 );
646 return;
647 }
648 }
649 }
650 }
651
652 let point_for_position = position_map.point_for_position(event.position);
653 let position = point_for_position.previous_valid;
654 if modifiers == COLUMNAR_SELECTION_MODIFIERS {
655 editor.select(
656 SelectPhase::BeginColumnar {
657 position,
658 reset: false,
659 goal_column: point_for_position.exact_unclipped.column(),
660 },
661 window,
662 cx,
663 );
664 } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.secondary()
665 {
666 editor.select(
667 SelectPhase::Extend {
668 position,
669 click_count,
670 },
671 window,
672 cx,
673 );
674 } else {
675 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
676 let multi_cursor_modifier = match multi_cursor_setting {
677 MultiCursorModifier::Alt => modifiers.alt,
678 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
679 };
680 editor.select(
681 SelectPhase::Begin {
682 position,
683 add: multi_cursor_modifier,
684 click_count,
685 },
686 window,
687 cx,
688 );
689 }
690 cx.stop_propagation();
691
692 if !is_singleton {
693 let display_row = (((event.position - gutter_hitbox.bounds.origin).y
694 + position_map.scroll_pixel_position.y)
695 / position_map.line_height) as u32;
696 let multi_buffer_row = position_map
697 .snapshot
698 .display_point_to_point(DisplayPoint::new(DisplayRow(display_row), 0), Bias::Right)
699 .row;
700 if line_numbers
701 .get(&MultiBufferRow(multi_buffer_row))
702 .and_then(|line_number| line_number.hitbox.as_ref())
703 .is_some_and(|hitbox| hitbox.contains(&event.position))
704 {
705 let scroll_position_row =
706 position_map.scroll_pixel_position.y / position_map.line_height;
707 let line_offset_from_top = display_row - scroll_position_row as u32;
708
709 editor.open_excerpts_common(
710 Some(JumpData::MultiBufferRow {
711 row: MultiBufferRow(multi_buffer_row),
712 line_offset_from_top,
713 }),
714 modifiers.alt,
715 window,
716 cx,
717 );
718 cx.stop_propagation();
719 }
720 }
721 }
722
723 fn mouse_right_down(
724 editor: &mut Editor,
725 event: &MouseDownEvent,
726 position_map: &PositionMap,
727 window: &mut Window,
728 cx: &mut Context<Editor>,
729 ) {
730 if position_map.gutter_hitbox.is_hovered(window) {
731 let gutter_right_padding = editor.gutter_dimensions.right_padding;
732 let hitbox = &position_map.gutter_hitbox;
733
734 if event.position.x <= hitbox.bounds.right() - gutter_right_padding {
735 let point_for_position = position_map.point_for_position(event.position);
736 editor.set_breakpoint_context_menu(
737 point_for_position.previous_valid.row(),
738 None,
739 event.position,
740 window,
741 cx,
742 );
743 }
744 return;
745 }
746
747 if !position_map.text_hitbox.is_hovered(window) {
748 return;
749 }
750
751 let point_for_position = position_map.point_for_position(event.position);
752 mouse_context_menu::deploy_context_menu(
753 editor,
754 Some(event.position),
755 point_for_position.previous_valid,
756 window,
757 cx,
758 );
759 cx.stop_propagation();
760 }
761
762 fn mouse_middle_down(
763 editor: &mut Editor,
764 event: &MouseDownEvent,
765 position_map: &PositionMap,
766 window: &mut Window,
767 cx: &mut Context<Editor>,
768 ) {
769 if !position_map.text_hitbox.is_hovered(window) || window.default_prevented() {
770 return;
771 }
772
773 let point_for_position = position_map.point_for_position(event.position);
774 let position = point_for_position.previous_valid;
775
776 editor.select(
777 SelectPhase::BeginColumnar {
778 position,
779 reset: true,
780 goal_column: point_for_position.exact_unclipped.column(),
781 },
782 window,
783 cx,
784 );
785 }
786
787 fn mouse_up(
788 editor: &mut Editor,
789 event: &MouseUpEvent,
790 position_map: &PositionMap,
791 window: &mut Window,
792 cx: &mut Context<Editor>,
793 ) {
794 let text_hitbox = &position_map.text_hitbox;
795 let end_selection = editor.has_pending_selection();
796 let pending_nonempty_selections = editor.has_pending_nonempty_selection();
797
798 if end_selection {
799 editor.select(SelectPhase::End, window, cx);
800 }
801
802 if end_selection && pending_nonempty_selections {
803 cx.stop_propagation();
804 } else if cfg!(any(target_os = "linux", target_os = "freebsd"))
805 && event.button == MouseButton::Middle
806 {
807 if !text_hitbox.is_hovered(window) || editor.read_only(cx) {
808 return;
809 }
810
811 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
812 if EditorSettings::get_global(cx).middle_click_paste {
813 if let Some(text) = cx.read_from_primary().and_then(|item| item.text()) {
814 let point_for_position = position_map.point_for_position(event.position);
815 let position = point_for_position.previous_valid;
816
817 editor.select(
818 SelectPhase::Begin {
819 position,
820 add: false,
821 click_count: 1,
822 },
823 window,
824 cx,
825 );
826 editor.insert(&text, window, cx);
827 }
828 cx.stop_propagation()
829 }
830 }
831 }
832
833 fn click(
834 editor: &mut Editor,
835 event: &ClickEvent,
836 position_map: &PositionMap,
837 window: &mut Window,
838 cx: &mut Context<Editor>,
839 ) {
840 let text_hitbox = &position_map.text_hitbox;
841 let pending_nonempty_selections = editor.has_pending_nonempty_selection();
842
843 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
844 let multi_cursor_modifier = match multi_cursor_setting {
845 MultiCursorModifier::Alt => event.modifiers().secondary(),
846 MultiCursorModifier::CmdOrCtrl => event.modifiers().alt,
847 };
848
849 if !pending_nonempty_selections && multi_cursor_modifier && text_hitbox.is_hovered(window) {
850 let point = position_map.point_for_position(event.up.position);
851 editor.handle_click_hovered_link(point, event.modifiers(), window, cx);
852
853 cx.stop_propagation();
854 }
855 }
856
857 fn mouse_dragged(
858 editor: &mut Editor,
859 event: &MouseMoveEvent,
860 position_map: &PositionMap,
861 window: &mut Window,
862 cx: &mut Context<Editor>,
863 ) {
864 if !editor.has_pending_selection() {
865 return;
866 }
867
868 let text_bounds = position_map.text_hitbox.bounds;
869 let point_for_position = position_map.point_for_position(event.position);
870 let mut scroll_delta = gpui::Point::<f32>::default();
871 let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
872 let top = text_bounds.origin.y + vertical_margin;
873 let bottom = text_bounds.bottom_left().y - vertical_margin;
874 if event.position.y < top {
875 scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y);
876 }
877 if event.position.y > bottom {
878 scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
879 }
880
881 // We need horizontal width of text
882 let style = editor.style.clone().unwrap_or_default();
883 let font_id = window.text_system().resolve_font(&style.text.font());
884 let font_size = style.text.font_size.to_pixels(window.rem_size());
885 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
886
887 let scroll_margin_x = EditorSettings::get_global(cx).horizontal_scroll_margin;
888
889 let scroll_space: Pixels = scroll_margin_x * em_width;
890
891 let left = text_bounds.origin.x + scroll_space;
892 let right = text_bounds.top_right().x - scroll_space;
893
894 if event.position.x < left {
895 scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
896 }
897 if event.position.x > right {
898 scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
899 }
900
901 editor.select(
902 SelectPhase::Update {
903 position: point_for_position.previous_valid,
904 goal_column: point_for_position.exact_unclipped.column(),
905 scroll_delta,
906 },
907 window,
908 cx,
909 );
910 }
911
912 fn mouse_moved(
913 editor: &mut Editor,
914 event: &MouseMoveEvent,
915 position_map: &PositionMap,
916 window: &mut Window,
917 cx: &mut Context<Editor>,
918 ) {
919 let text_hitbox = &position_map.text_hitbox;
920 let gutter_hitbox = &position_map.gutter_hitbox;
921 let modifiers = event.modifiers;
922 let gutter_hovered = gutter_hitbox.is_hovered(window);
923 editor.set_gutter_hovered(gutter_hovered, cx);
924 editor.mouse_cursor_hidden = false;
925
926 if gutter_hovered {
927 let new_point = position_map
928 .point_for_position(event.position)
929 .previous_valid;
930 let buffer_anchor = position_map
931 .snapshot
932 .display_point_to_anchor(new_point, Bias::Left);
933
934 if position_map
935 .snapshot
936 .buffer_snapshot
937 .buffer_for_excerpt(buffer_anchor.excerpt_id)
938 .is_some_and(|buffer| buffer.file().is_some())
939 {
940 let was_hovered = editor.gutter_breakpoint_indicator.0.is_some();
941 let is_visible = editor
942 .gutter_breakpoint_indicator
943 .0
944 .map_or(false, |(_, is_active)| is_active);
945 editor.gutter_breakpoint_indicator.0 = Some((new_point, is_visible));
946
947 editor.gutter_breakpoint_indicator.1.get_or_insert_with(|| {
948 cx.spawn(async move |this, cx| {
949 if !was_hovered {
950 cx.background_executor()
951 .timer(Duration::from_millis(200))
952 .await;
953 }
954
955 this.update(cx, |this, cx| {
956 if let Some((_, is_active)) =
957 this.gutter_breakpoint_indicator.0.as_mut()
958 {
959 *is_active = true;
960 }
961
962 cx.notify();
963 })
964 .ok();
965 })
966 });
967 } else {
968 editor.gutter_breakpoint_indicator = (None, None);
969 }
970 } else {
971 editor.gutter_breakpoint_indicator = (None, None);
972 }
973
974 cx.notify();
975
976 // Don't trigger hover popover if mouse is hovering over context menu
977 if text_hitbox.is_hovered(window) {
978 let point_for_position = position_map.point_for_position(event.position);
979
980 editor.update_hovered_link(
981 point_for_position,
982 &position_map.snapshot,
983 modifiers,
984 window,
985 cx,
986 );
987
988 if let Some(point) = point_for_position.as_valid() {
989 let anchor = position_map
990 .snapshot
991 .buffer_snapshot
992 .anchor_before(point.to_offset(&position_map.snapshot, Bias::Left));
993 hover_at(editor, Some(anchor), window, cx);
994 Self::update_visible_cursor(editor, point, position_map, window, cx);
995 } else {
996 hover_at(editor, None, window, cx);
997 }
998 } else {
999 editor.hide_hovered_link(cx);
1000 hover_at(editor, None, window, cx);
1001 if gutter_hovered {
1002 cx.stop_propagation();
1003 }
1004 }
1005 }
1006
1007 fn update_visible_cursor(
1008 editor: &mut Editor,
1009 point: DisplayPoint,
1010 position_map: &PositionMap,
1011 window: &mut Window,
1012 cx: &mut Context<Editor>,
1013 ) {
1014 let snapshot = &position_map.snapshot;
1015 let Some(hub) = editor.collaboration_hub() else {
1016 return;
1017 };
1018 let start = snapshot.display_snapshot.clip_point(
1019 DisplayPoint::new(point.row(), point.column().saturating_sub(1)),
1020 Bias::Left,
1021 );
1022 let end = snapshot.display_snapshot.clip_point(
1023 DisplayPoint::new(
1024 point.row(),
1025 (point.column() + 1).min(snapshot.line_len(point.row())),
1026 ),
1027 Bias::Right,
1028 );
1029
1030 let range = snapshot
1031 .buffer_snapshot
1032 .anchor_at(start.to_point(&snapshot.display_snapshot), Bias::Left)
1033 ..snapshot
1034 .buffer_snapshot
1035 .anchor_at(end.to_point(&snapshot.display_snapshot), Bias::Right);
1036
1037 let Some(selection) = snapshot.remote_selections_in_range(&range, hub, cx).next() else {
1038 return;
1039 };
1040 let key = crate::HoveredCursor {
1041 replica_id: selection.replica_id,
1042 selection_id: selection.selection.id,
1043 };
1044 editor.hovered_cursors.insert(
1045 key.clone(),
1046 cx.spawn_in(window, async move |editor, cx| {
1047 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
1048 editor
1049 .update(cx, |editor, cx| {
1050 editor.hovered_cursors.remove(&key);
1051 cx.notify();
1052 })
1053 .ok();
1054 }),
1055 );
1056 cx.notify()
1057 }
1058
1059 fn layout_selections(
1060 &self,
1061 start_anchor: Anchor,
1062 end_anchor: Anchor,
1063 local_selections: &[Selection<Point>],
1064 snapshot: &EditorSnapshot,
1065 start_row: DisplayRow,
1066 end_row: DisplayRow,
1067 window: &mut Window,
1068 cx: &mut App,
1069 ) -> (
1070 Vec<(PlayerColor, Vec<SelectionLayout>)>,
1071 BTreeMap<DisplayRow, LineHighlightSpec>,
1072 Option<DisplayPoint>,
1073 ) {
1074 let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
1075 let mut active_rows = BTreeMap::new();
1076 let mut newest_selection_head = None;
1077 self.editor.update(cx, |editor, cx| {
1078 if editor.show_local_selections {
1079 let mut layouts = Vec::new();
1080 let newest = editor.selections.newest(cx);
1081 for selection in local_selections.iter().cloned() {
1082 let is_empty = selection.start == selection.end;
1083 let is_newest = selection == newest;
1084
1085 let layout = SelectionLayout::new(
1086 selection,
1087 editor.selections.line_mode,
1088 editor.cursor_shape,
1089 &snapshot.display_snapshot,
1090 is_newest,
1091 editor.leader_peer_id.is_none(),
1092 None,
1093 );
1094 if is_newest {
1095 newest_selection_head = Some(layout.head);
1096 }
1097
1098 for row in cmp::max(layout.active_rows.start.0, start_row.0)
1099 ..=cmp::min(layout.active_rows.end.0, end_row.0)
1100 {
1101 let contains_non_empty_selection = active_rows
1102 .entry(DisplayRow(row))
1103 .or_insert_with(LineHighlightSpec::default);
1104 contains_non_empty_selection.selection |= !is_empty;
1105 }
1106 layouts.push(layout);
1107 }
1108
1109 let player = editor.current_user_player_color(cx);
1110 selections.push((player, layouts));
1111 }
1112
1113 if let Some(collaboration_hub) = &editor.collaboration_hub {
1114 // When following someone, render the local selections in their color.
1115 if let Some(leader_id) = editor.leader_peer_id {
1116 if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id)
1117 {
1118 if let Some(participant_index) = collaboration_hub
1119 .user_participant_indices(cx)
1120 .get(&collaborator.user_id)
1121 {
1122 if let Some((local_selection_style, _)) = selections.first_mut() {
1123 *local_selection_style = cx
1124 .theme()
1125 .players()
1126 .color_for_participant(participant_index.0);
1127 }
1128 }
1129 }
1130 }
1131
1132 let mut remote_selections = HashMap::default();
1133 for selection in snapshot.remote_selections_in_range(
1134 &(start_anchor..end_anchor),
1135 collaboration_hub.as_ref(),
1136 cx,
1137 ) {
1138 let selection_style =
1139 Self::get_participant_color(selection.participant_index, cx);
1140
1141 // Don't re-render the leader's selections, since the local selections
1142 // match theirs.
1143 if Some(selection.peer_id) == editor.leader_peer_id {
1144 continue;
1145 }
1146 let key = HoveredCursor {
1147 replica_id: selection.replica_id,
1148 selection_id: selection.selection.id,
1149 };
1150
1151 let is_shown =
1152 editor.show_cursor_names || editor.hovered_cursors.contains_key(&key);
1153
1154 remote_selections
1155 .entry(selection.replica_id)
1156 .or_insert((selection_style, Vec::new()))
1157 .1
1158 .push(SelectionLayout::new(
1159 selection.selection,
1160 selection.line_mode,
1161 selection.cursor_shape,
1162 &snapshot.display_snapshot,
1163 false,
1164 false,
1165 if is_shown { selection.user_name } else { None },
1166 ));
1167 }
1168
1169 selections.extend(remote_selections.into_values());
1170 } else if !editor.is_focused(window) && editor.show_cursor_when_unfocused {
1171 let layouts = snapshot
1172 .buffer_snapshot
1173 .selections_in_range(&(start_anchor..end_anchor), true)
1174 .map(move |(_, line_mode, cursor_shape, selection)| {
1175 SelectionLayout::new(
1176 selection,
1177 line_mode,
1178 cursor_shape,
1179 &snapshot.display_snapshot,
1180 false,
1181 false,
1182 None,
1183 )
1184 })
1185 .collect::<Vec<_>>();
1186 let player = editor.current_user_player_color(cx);
1187 selections.push((player, layouts));
1188 }
1189 });
1190 (selections, active_rows, newest_selection_head)
1191 }
1192
1193 fn collect_cursors(
1194 &self,
1195 snapshot: &EditorSnapshot,
1196 cx: &mut App,
1197 ) -> Vec<(DisplayPoint, Hsla)> {
1198 let editor = self.editor.read(cx);
1199 let mut cursors = Vec::new();
1200 let mut skip_local = false;
1201 let mut add_cursor = |anchor: Anchor, color| {
1202 cursors.push((anchor.to_display_point(&snapshot.display_snapshot), color));
1203 };
1204 // Remote cursors
1205 if let Some(collaboration_hub) = &editor.collaboration_hub {
1206 for remote_selection in snapshot.remote_selections_in_range(
1207 &(Anchor::min()..Anchor::max()),
1208 collaboration_hub.deref(),
1209 cx,
1210 ) {
1211 let color = Self::get_participant_color(remote_selection.participant_index, cx);
1212 add_cursor(remote_selection.selection.head(), color.cursor);
1213 if Some(remote_selection.peer_id) == editor.leader_peer_id {
1214 skip_local = true;
1215 }
1216 }
1217 }
1218 // Local cursors
1219 if !skip_local {
1220 let color = cx.theme().players().local().cursor;
1221 editor.selections.disjoint.iter().for_each(|selection| {
1222 add_cursor(selection.head(), color);
1223 });
1224 if let Some(ref selection) = editor.selections.pending_anchor() {
1225 add_cursor(selection.head(), color);
1226 }
1227 }
1228 cursors
1229 }
1230
1231 fn layout_visible_cursors(
1232 &self,
1233 snapshot: &EditorSnapshot,
1234 selections: &[(PlayerColor, Vec<SelectionLayout>)],
1235 row_block_types: &HashMap<DisplayRow, bool>,
1236 visible_display_row_range: Range<DisplayRow>,
1237 line_layouts: &[LineWithInvisibles],
1238 text_hitbox: &Hitbox,
1239 content_origin: gpui::Point<Pixels>,
1240 scroll_position: gpui::Point<f32>,
1241 scroll_pixel_position: gpui::Point<Pixels>,
1242 line_height: Pixels,
1243 em_width: Pixels,
1244 em_advance: Pixels,
1245 autoscroll_containing_element: bool,
1246 window: &mut Window,
1247 cx: &mut App,
1248 ) -> Vec<CursorLayout> {
1249 let mut autoscroll_bounds = None;
1250 let cursor_layouts = self.editor.update(cx, |editor, cx| {
1251 let mut cursors = Vec::new();
1252
1253 let show_local_cursors = editor.show_local_cursors(window, cx);
1254
1255 for (player_color, selections) in selections {
1256 for selection in selections {
1257 let cursor_position = selection.head;
1258
1259 let in_range = visible_display_row_range.contains(&cursor_position.row());
1260 if (selection.is_local && !show_local_cursors)
1261 || !in_range
1262 || row_block_types.get(&cursor_position.row()) == Some(&true)
1263 {
1264 continue;
1265 }
1266
1267 let cursor_row_layout = &line_layouts
1268 [cursor_position.row().minus(visible_display_row_range.start) as usize];
1269 let cursor_column = cursor_position.column() as usize;
1270
1271 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
1272 let mut block_width =
1273 cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
1274 if block_width == Pixels::ZERO {
1275 block_width = em_advance;
1276 }
1277 let block_text = if let CursorShape::Block = selection.cursor_shape {
1278 snapshot
1279 .grapheme_at(cursor_position)
1280 .or_else(|| {
1281 if cursor_column == 0 {
1282 snapshot.placeholder_text().and_then(|s| {
1283 s.graphemes(true).next().map(|s| s.to_string().into())
1284 })
1285 } else {
1286 None
1287 }
1288 })
1289 .and_then(|text| {
1290 let len = text.len();
1291
1292 let font = cursor_row_layout
1293 .font_id_for_index(cursor_column)
1294 .and_then(|cursor_font_id| {
1295 window.text_system().get_font_for_id(cursor_font_id)
1296 })
1297 .unwrap_or(self.style.text.font());
1298
1299 // Invert the text color for the block cursor. Ensure that the text
1300 // color is opaque enough to be visible against the background color.
1301 //
1302 // 0.75 is an arbitrary threshold to determine if the background color is
1303 // opaque enough to use as a text color.
1304 //
1305 // TODO: In the future we should ensure themes have a `text_inverse` color.
1306 let color = if cx.theme().colors().editor_background.a < 0.75 {
1307 match cx.theme().appearance {
1308 Appearance::Dark => Hsla::black(),
1309 Appearance::Light => Hsla::white(),
1310 }
1311 } else {
1312 cx.theme().colors().editor_background
1313 };
1314
1315 window
1316 .text_system()
1317 .shape_line(
1318 text,
1319 cursor_row_layout.font_size,
1320 &[TextRun {
1321 len,
1322 font,
1323 color,
1324 background_color: None,
1325 strikethrough: None,
1326 underline: None,
1327 }],
1328 )
1329 .log_err()
1330 })
1331 } else {
1332 None
1333 };
1334
1335 let x = cursor_character_x - scroll_pixel_position.x;
1336 let y = (cursor_position.row().as_f32()
1337 - scroll_pixel_position.y / line_height)
1338 * line_height;
1339 if selection.is_newest {
1340 editor.pixel_position_of_newest_cursor = Some(point(
1341 text_hitbox.origin.x + x + block_width / 2.,
1342 text_hitbox.origin.y + y + line_height / 2.,
1343 ));
1344
1345 if autoscroll_containing_element {
1346 let top = text_hitbox.origin.y
1347 + (cursor_position.row().as_f32() - scroll_position.y - 3.).max(0.)
1348 * line_height;
1349 let left = text_hitbox.origin.x
1350 + (cursor_position.column() as f32 - scroll_position.x - 3.)
1351 .max(0.)
1352 * em_width;
1353
1354 let bottom = text_hitbox.origin.y
1355 + (cursor_position.row().as_f32() - scroll_position.y + 4.)
1356 * line_height;
1357 let right = text_hitbox.origin.x
1358 + (cursor_position.column() as f32 - scroll_position.x + 4.)
1359 * em_width;
1360
1361 autoscroll_bounds =
1362 Some(Bounds::from_corners(point(left, top), point(right, bottom)))
1363 }
1364 }
1365
1366 let mut cursor = CursorLayout {
1367 color: player_color.cursor,
1368 block_width,
1369 origin: point(x, y),
1370 line_height,
1371 shape: selection.cursor_shape,
1372 block_text,
1373 cursor_name: None,
1374 };
1375 let cursor_name = selection.user_name.clone().map(|name| CursorName {
1376 string: name,
1377 color: self.style.background,
1378 is_top_row: cursor_position.row().0 == 0,
1379 });
1380 cursor.layout(content_origin, cursor_name, window, cx);
1381 cursors.push(cursor);
1382 }
1383 }
1384
1385 cursors
1386 });
1387
1388 if let Some(bounds) = autoscroll_bounds {
1389 window.request_autoscroll(bounds);
1390 }
1391
1392 cursor_layouts
1393 }
1394
1395 fn layout_scrollbars(
1396 &self,
1397 snapshot: &EditorSnapshot,
1398 scrollbar_layout_information: ScrollbarLayoutInformation,
1399 content_offset: gpui::Point<Pixels>,
1400 scroll_position: gpui::Point<f32>,
1401 non_visible_cursors: bool,
1402 window: &mut Window,
1403 cx: &mut App,
1404 ) -> Option<EditorScrollbars> {
1405 if !snapshot.mode.is_full() {
1406 return None;
1407 }
1408
1409 // If a drag took place after we started dragging the scrollbar,
1410 // cancel the scrollbar drag.
1411 if cx.has_active_drag() {
1412 self.editor.update(cx, |editor, cx| {
1413 editor.scroll_manager.reset_scrollbar_dragging_state(cx)
1414 });
1415 }
1416
1417 let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
1418 let show_scrollbars = self.editor.read(cx).show_scrollbars
1419 && match scrollbar_settings.show {
1420 ShowScrollbar::Auto => {
1421 let editor = self.editor.read(cx);
1422 let is_singleton = editor.is_singleton(cx);
1423 // Git
1424 (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_diff_hunks())
1425 ||
1426 // Buffer Search Results
1427 (is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
1428 ||
1429 // Selected Text Occurrences
1430 (is_singleton && scrollbar_settings.selected_text && editor.has_background_highlights::<SelectedTextHighlight>())
1431 ||
1432 // Selected Symbol Occurrences
1433 (is_singleton && scrollbar_settings.selected_symbol && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
1434 ||
1435 // Diagnostics
1436 (is_singleton && scrollbar_settings.diagnostics != ScrollbarDiagnostics::None && snapshot.buffer_snapshot.has_diagnostics())
1437 ||
1438 // Cursors out of sight
1439 non_visible_cursors
1440 ||
1441 // Scrollmanager
1442 editor.scroll_manager.scrollbars_visible()
1443 }
1444 ShowScrollbar::System => self.editor.read(cx).scroll_manager.scrollbars_visible(),
1445 ShowScrollbar::Always => true,
1446 ShowScrollbar::Never => return None,
1447 };
1448
1449 Some(EditorScrollbars::from_scrollbar_axes(
1450 scrollbar_settings.axes,
1451 &scrollbar_layout_information,
1452 content_offset,
1453 scroll_position,
1454 self.style.scrollbar_width,
1455 show_scrollbars,
1456 window,
1457 ))
1458 }
1459
1460 fn prepaint_crease_toggles(
1461 &self,
1462 crease_toggles: &mut [Option<AnyElement>],
1463 line_height: Pixels,
1464 gutter_dimensions: &GutterDimensions,
1465 gutter_settings: crate::editor_settings::Gutter,
1466 scroll_pixel_position: gpui::Point<Pixels>,
1467 gutter_hitbox: &Hitbox,
1468 window: &mut Window,
1469 cx: &mut App,
1470 ) {
1471 for (ix, crease_toggle) in crease_toggles.iter_mut().enumerate() {
1472 if let Some(crease_toggle) = crease_toggle {
1473 debug_assert!(gutter_settings.folds);
1474 let available_space = size(
1475 AvailableSpace::MinContent,
1476 AvailableSpace::Definite(line_height * 0.55),
1477 );
1478 let crease_toggle_size = crease_toggle.layout_as_root(available_space, window, cx);
1479
1480 let position = point(
1481 gutter_dimensions.width - gutter_dimensions.right_padding,
1482 ix as f32 * line_height - (scroll_pixel_position.y % line_height),
1483 );
1484 let centering_offset = point(
1485 (gutter_dimensions.fold_area_width() - crease_toggle_size.width) / 2.,
1486 (line_height - crease_toggle_size.height) / 2.,
1487 );
1488 let origin = gutter_hitbox.origin + position + centering_offset;
1489 crease_toggle.prepaint_as_root(origin, available_space, window, cx);
1490 }
1491 }
1492 }
1493
1494 fn prepaint_expand_toggles(
1495 &self,
1496 expand_toggles: &mut [Option<(AnyElement, gpui::Point<Pixels>)>],
1497 window: &mut Window,
1498 cx: &mut App,
1499 ) {
1500 for (expand_toggle, origin) in expand_toggles.iter_mut().flatten() {
1501 let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
1502 expand_toggle.layout_as_root(available_space, window, cx);
1503 expand_toggle.prepaint_as_root(*origin, available_space, window, cx);
1504 }
1505 }
1506
1507 fn prepaint_crease_trailers(
1508 &self,
1509 trailers: Vec<Option<AnyElement>>,
1510 lines: &[LineWithInvisibles],
1511 line_height: Pixels,
1512 content_origin: gpui::Point<Pixels>,
1513 scroll_pixel_position: gpui::Point<Pixels>,
1514 em_width: Pixels,
1515 window: &mut Window,
1516 cx: &mut App,
1517 ) -> Vec<Option<CreaseTrailerLayout>> {
1518 trailers
1519 .into_iter()
1520 .enumerate()
1521 .map(|(ix, element)| {
1522 let mut element = element?;
1523 let available_space = size(
1524 AvailableSpace::MinContent,
1525 AvailableSpace::Definite(line_height),
1526 );
1527 let size = element.layout_as_root(available_space, window, cx);
1528
1529 let line = &lines[ix];
1530 let padding = if line.width == Pixels::ZERO {
1531 Pixels::ZERO
1532 } else {
1533 4. * em_width
1534 };
1535 let position = point(
1536 scroll_pixel_position.x + line.width + padding,
1537 ix as f32 * line_height - (scroll_pixel_position.y % line_height),
1538 );
1539 let centering_offset = point(px(0.), (line_height - size.height) / 2.);
1540 let origin = content_origin + position + centering_offset;
1541 element.prepaint_as_root(origin, available_space, window, cx);
1542 Some(CreaseTrailerLayout {
1543 element,
1544 bounds: Bounds::new(origin, size),
1545 })
1546 })
1547 .collect()
1548 }
1549
1550 // Folds contained in a hunk are ignored apart from shrinking visual size
1551 // If a fold contains any hunks then that fold line is marked as modified
1552 fn layout_gutter_diff_hunks(
1553 &self,
1554 line_height: Pixels,
1555 gutter_hitbox: &Hitbox,
1556 display_rows: Range<DisplayRow>,
1557 snapshot: &EditorSnapshot,
1558 window: &mut Window,
1559 cx: &mut App,
1560 ) -> Vec<(DisplayDiffHunk, Option<Hitbox>)> {
1561 let folded_buffers = self.editor.read(cx).folded_buffers(cx);
1562 let mut display_hunks = snapshot
1563 .display_diff_hunks_for_rows(display_rows, folded_buffers)
1564 .map(|hunk| (hunk, None))
1565 .collect::<Vec<_>>();
1566 let git_gutter_setting = ProjectSettings::get_global(cx)
1567 .git
1568 .git_gutter
1569 .unwrap_or_default();
1570 if let GitGutterSetting::TrackedFiles = git_gutter_setting {
1571 for (hunk, hitbox) in &mut display_hunks {
1572 if matches!(hunk, DisplayDiffHunk::Unfolded { .. }) {
1573 let hunk_bounds =
1574 Self::diff_hunk_bounds(snapshot, line_height, gutter_hitbox.bounds, hunk);
1575 *hitbox = Some(window.insert_hitbox(hunk_bounds, true));
1576 }
1577 }
1578 }
1579
1580 display_hunks
1581 }
1582
1583 fn layout_inline_diagnostics(
1584 &self,
1585 line_layouts: &[LineWithInvisibles],
1586 crease_trailers: &[Option<CreaseTrailerLayout>],
1587 row_block_types: &HashMap<DisplayRow, bool>,
1588 content_origin: gpui::Point<Pixels>,
1589 scroll_pixel_position: gpui::Point<Pixels>,
1590 inline_completion_popover_origin: Option<gpui::Point<Pixels>>,
1591 start_row: DisplayRow,
1592 end_row: DisplayRow,
1593 line_height: Pixels,
1594 em_width: Pixels,
1595 style: &EditorStyle,
1596 window: &mut Window,
1597 cx: &mut App,
1598 ) -> HashMap<DisplayRow, AnyElement> {
1599 let max_severity = ProjectSettings::get_global(cx)
1600 .diagnostics
1601 .inline
1602 .max_severity
1603 .map_or(DiagnosticSeverity::HINT, |severity| match severity {
1604 project_settings::DiagnosticSeverity::Error => DiagnosticSeverity::ERROR,
1605 project_settings::DiagnosticSeverity::Warning => DiagnosticSeverity::WARNING,
1606 project_settings::DiagnosticSeverity::Info => DiagnosticSeverity::INFORMATION,
1607 project_settings::DiagnosticSeverity::Hint => DiagnosticSeverity::HINT,
1608 });
1609
1610 let active_diagnostics_group = self
1611 .editor
1612 .read(cx)
1613 .active_diagnostics
1614 .as_ref()
1615 .map(|active_diagnostics| active_diagnostics.group_id);
1616
1617 let diagnostics_by_rows = self.editor.update(cx, |editor, cx| {
1618 let snapshot = editor.snapshot(window, cx);
1619 editor
1620 .inline_diagnostics
1621 .iter()
1622 .filter(|(_, diagnostic)| diagnostic.severity <= max_severity)
1623 .filter(|(_, diagnostic)| match active_diagnostics_group {
1624 Some(active_diagnostics_group) => {
1625 // Active diagnostics are all shown in the editor already, no need to display them inline
1626 diagnostic.group_id != active_diagnostics_group
1627 }
1628 None => true,
1629 })
1630 .map(|(point, diag)| (point.to_display_point(&snapshot), diag.clone()))
1631 .skip_while(|(point, _)| point.row() < start_row)
1632 .take_while(|(point, _)| point.row() < end_row)
1633 .filter(|(point, _)| !row_block_types.contains_key(&point.row()))
1634 .fold(HashMap::default(), |mut acc, (point, diagnostic)| {
1635 acc.entry(point.row())
1636 .or_insert_with(Vec::new)
1637 .push(diagnostic);
1638 acc
1639 })
1640 });
1641
1642 if diagnostics_by_rows.is_empty() {
1643 return HashMap::default();
1644 }
1645
1646 let severity_to_color = |sev: &DiagnosticSeverity| match sev {
1647 &DiagnosticSeverity::ERROR => Color::Error,
1648 &DiagnosticSeverity::WARNING => Color::Warning,
1649 &DiagnosticSeverity::INFORMATION => Color::Info,
1650 &DiagnosticSeverity::HINT => Color::Hint,
1651 _ => Color::Error,
1652 };
1653
1654 let padding = ProjectSettings::get_global(cx).diagnostics.inline.padding as f32 * em_width;
1655 let min_x = ProjectSettings::get_global(cx)
1656 .diagnostics
1657 .inline
1658 .min_column as f32
1659 * em_width;
1660
1661 let mut elements = HashMap::default();
1662 for (row, mut diagnostics) in diagnostics_by_rows {
1663 diagnostics.sort_by_key(|diagnostic| {
1664 (
1665 diagnostic.severity,
1666 std::cmp::Reverse(diagnostic.is_primary),
1667 diagnostic.start.row,
1668 diagnostic.start.column,
1669 )
1670 });
1671
1672 let Some(diagnostic_to_render) = diagnostics
1673 .iter()
1674 .find(|diagnostic| diagnostic.is_primary)
1675 .or_else(|| diagnostics.first())
1676 else {
1677 continue;
1678 };
1679
1680 let pos_y = content_origin.y
1681 + line_height * (row.0 as f32 - scroll_pixel_position.y / line_height);
1682
1683 let window_ix = row.0.saturating_sub(start_row.0) as usize;
1684 let pos_x = {
1685 let crease_trailer_layout = &crease_trailers[window_ix];
1686 let line_layout = &line_layouts[window_ix];
1687
1688 let line_end = if let Some(crease_trailer) = crease_trailer_layout {
1689 crease_trailer.bounds.right()
1690 } else {
1691 content_origin.x - scroll_pixel_position.x + line_layout.width
1692 };
1693
1694 let padded_line = line_end + padding;
1695 let min_start = content_origin.x - scroll_pixel_position.x + min_x;
1696
1697 cmp::max(padded_line, min_start)
1698 };
1699
1700 let behind_inline_completion_popover = inline_completion_popover_origin
1701 .as_ref()
1702 .map_or(false, |inline_completion_popover_origin| {
1703 (pos_y..pos_y + line_height).contains(&inline_completion_popover_origin.y)
1704 });
1705 let opacity = if behind_inline_completion_popover {
1706 0.5
1707 } else {
1708 1.0
1709 };
1710
1711 let mut element = h_flex()
1712 .id(("diagnostic", row.0))
1713 .h(line_height)
1714 .w_full()
1715 .px_1()
1716 .rounded_xs()
1717 .opacity(opacity)
1718 .bg(severity_to_color(&diagnostic_to_render.severity)
1719 .color(cx)
1720 .opacity(0.05))
1721 .text_color(severity_to_color(&diagnostic_to_render.severity).color(cx))
1722 .text_sm()
1723 .font_family(style.text.font().family)
1724 .child(diagnostic_to_render.message.clone())
1725 .into_any();
1726
1727 element.prepaint_as_root(point(pos_x, pos_y), AvailableSpace::min_size(), window, cx);
1728
1729 elements.insert(row, element);
1730 }
1731
1732 elements
1733 }
1734
1735 fn layout_inline_blame(
1736 &self,
1737 display_row: DisplayRow,
1738 row_info: &RowInfo,
1739 line_layout: &LineWithInvisibles,
1740 crease_trailer: Option<&CreaseTrailerLayout>,
1741 em_width: Pixels,
1742 content_origin: gpui::Point<Pixels>,
1743 scroll_pixel_position: gpui::Point<Pixels>,
1744 line_height: Pixels,
1745 window: &mut Window,
1746 cx: &mut App,
1747 ) -> Option<AnyElement> {
1748 if !self
1749 .editor
1750 .update(cx, |editor, cx| editor.render_git_blame_inline(window, cx))
1751 {
1752 return None;
1753 }
1754
1755 let editor = self.editor.read(cx);
1756 let blame = editor.blame.clone()?;
1757 let padding = {
1758 const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
1759 const INLINE_ACCEPT_SUGGESTION_EM_WIDTHS: f32 = 14.;
1760
1761 let mut padding = INLINE_BLAME_PADDING_EM_WIDTHS;
1762
1763 if let Some(inline_completion) = editor.active_inline_completion.as_ref() {
1764 match &inline_completion.completion {
1765 InlineCompletion::Edit {
1766 display_mode: EditDisplayMode::TabAccept,
1767 ..
1768 } => padding += INLINE_ACCEPT_SUGGESTION_EM_WIDTHS,
1769 _ => {}
1770 }
1771 }
1772
1773 padding * em_width
1774 };
1775
1776 let workspace = editor.workspace()?.downgrade();
1777 let blame_entry = blame
1778 .update(cx, |blame, cx| {
1779 blame.blame_for_rows(&[*row_info], cx).next()
1780 })
1781 .flatten()?;
1782
1783 let mut element = render_inline_blame_entry(
1784 self.editor.clone(),
1785 workspace,
1786 &blame,
1787 blame_entry,
1788 &self.style,
1789 cx,
1790 )?;
1791
1792 let start_y = content_origin.y
1793 + line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height);
1794
1795 let start_x = {
1796 let line_end = if let Some(crease_trailer) = crease_trailer {
1797 crease_trailer.bounds.right()
1798 } else {
1799 content_origin.x - scroll_pixel_position.x + line_layout.width
1800 };
1801
1802 let padded_line_end = line_end + padding;
1803
1804 let min_column_in_pixels = ProjectSettings::get_global(cx)
1805 .git
1806 .inline_blame
1807 .and_then(|settings| settings.min_column)
1808 .map(|col| self.column_pixels(col as usize, window, cx))
1809 .unwrap_or(px(0.));
1810 let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels;
1811
1812 cmp::max(padded_line_end, min_start)
1813 };
1814
1815 let absolute_offset = point(start_x, start_y);
1816 element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), window, cx);
1817
1818 Some(element)
1819 }
1820
1821 fn layout_blame_entries(
1822 &self,
1823 buffer_rows: &[RowInfo],
1824 em_width: Pixels,
1825 scroll_position: gpui::Point<f32>,
1826 line_height: Pixels,
1827 gutter_hitbox: &Hitbox,
1828 max_width: Option<Pixels>,
1829 window: &mut Window,
1830 cx: &mut App,
1831 ) -> Option<Vec<AnyElement>> {
1832 if !self
1833 .editor
1834 .update(cx, |editor, cx| editor.render_git_blame_gutter(cx))
1835 {
1836 return None;
1837 }
1838
1839 let blame = self.editor.read(cx).blame.clone()?;
1840 let workspace = self.editor.read(cx).workspace()?;
1841 let blamed_rows: Vec<_> = blame.update(cx, |blame, cx| {
1842 blame.blame_for_rows(buffer_rows, cx).collect()
1843 });
1844
1845 let width = if let Some(max_width) = max_width {
1846 AvailableSpace::Definite(max_width)
1847 } else {
1848 AvailableSpace::MaxContent
1849 };
1850 let scroll_top = scroll_position.y * line_height;
1851 let start_x = em_width;
1852
1853 let mut last_used_color: Option<(PlayerColor, Oid)> = None;
1854 let blame_renderer = cx.global::<GlobalBlameRenderer>().0.clone();
1855
1856 let shaped_lines = blamed_rows
1857 .into_iter()
1858 .enumerate()
1859 .flat_map(|(ix, blame_entry)| {
1860 let mut element = render_blame_entry(
1861 ix,
1862 &blame,
1863 blame_entry?,
1864 &self.style,
1865 &mut last_used_color,
1866 self.editor.clone(),
1867 workspace.clone(),
1868 blame_renderer.clone(),
1869 cx,
1870 )?;
1871
1872 let start_y = ix as f32 * line_height - (scroll_top % line_height);
1873 let absolute_offset = gutter_hitbox.origin + point(start_x, start_y);
1874
1875 element.prepaint_as_root(
1876 absolute_offset,
1877 size(width, AvailableSpace::MinContent),
1878 window,
1879 cx,
1880 );
1881
1882 Some(element)
1883 })
1884 .collect();
1885
1886 Some(shaped_lines)
1887 }
1888
1889 fn layout_indent_guides(
1890 &self,
1891 content_origin: gpui::Point<Pixels>,
1892 text_origin: gpui::Point<Pixels>,
1893 visible_buffer_range: Range<MultiBufferRow>,
1894 scroll_pixel_position: gpui::Point<Pixels>,
1895 line_height: Pixels,
1896 snapshot: &DisplaySnapshot,
1897 window: &mut Window,
1898 cx: &mut App,
1899 ) -> Option<Vec<IndentGuideLayout>> {
1900 let indent_guides = self.editor.update(cx, |editor, cx| {
1901 editor.indent_guides(visible_buffer_range, snapshot, cx)
1902 })?;
1903
1904 let active_indent_guide_indices = self.editor.update(cx, |editor, cx| {
1905 editor
1906 .find_active_indent_guide_indices(&indent_guides, snapshot, window, cx)
1907 .unwrap_or_default()
1908 });
1909
1910 Some(
1911 indent_guides
1912 .into_iter()
1913 .enumerate()
1914 .filter_map(|(i, indent_guide)| {
1915 let single_indent_width =
1916 self.column_pixels(indent_guide.tab_size as usize, window, cx);
1917 let total_width = single_indent_width * indent_guide.depth as f32;
1918 let start_x = content_origin.x + total_width - scroll_pixel_position.x;
1919 if start_x >= text_origin.x {
1920 let (offset_y, length) = Self::calculate_indent_guide_bounds(
1921 indent_guide.start_row..indent_guide.end_row,
1922 line_height,
1923 snapshot,
1924 );
1925
1926 let start_y = content_origin.y + offset_y - scroll_pixel_position.y;
1927
1928 Some(IndentGuideLayout {
1929 origin: point(start_x, start_y),
1930 length,
1931 single_indent_width,
1932 depth: indent_guide.depth,
1933 active: active_indent_guide_indices.contains(&i),
1934 settings: indent_guide.settings,
1935 })
1936 } else {
1937 None
1938 }
1939 })
1940 .collect(),
1941 )
1942 }
1943
1944 fn calculate_indent_guide_bounds(
1945 row_range: Range<MultiBufferRow>,
1946 line_height: Pixels,
1947 snapshot: &DisplaySnapshot,
1948 ) -> (gpui::Pixels, gpui::Pixels) {
1949 let start_point = Point::new(row_range.start.0, 0);
1950 let end_point = Point::new(row_range.end.0, 0);
1951
1952 let row_range = start_point.to_display_point(snapshot).row()
1953 ..end_point.to_display_point(snapshot).row();
1954
1955 let mut prev_line = start_point;
1956 prev_line.row = prev_line.row.saturating_sub(1);
1957 let prev_line = prev_line.to_display_point(snapshot).row();
1958
1959 let mut cons_line = end_point;
1960 cons_line.row += 1;
1961 let cons_line = cons_line.to_display_point(snapshot).row();
1962
1963 let mut offset_y = row_range.start.0 as f32 * line_height;
1964 let mut length = (cons_line.0.saturating_sub(row_range.start.0)) as f32 * line_height;
1965
1966 // If we are at the end of the buffer, ensure that the indent guide extends to the end of the line.
1967 if row_range.end == cons_line {
1968 length += line_height;
1969 }
1970
1971 // If there is a block (e.g. diagnostic) in between the start of the indent guide and the line above,
1972 // we want to extend the indent guide to the start of the block.
1973 let mut block_height = 0;
1974 let mut block_offset = 0;
1975 let mut found_excerpt_header = false;
1976 for (_, block) in snapshot.blocks_in_range(prev_line..row_range.start) {
1977 if matches!(block, Block::ExcerptBoundary { .. }) {
1978 found_excerpt_header = true;
1979 break;
1980 }
1981 block_offset += block.height();
1982 block_height += block.height();
1983 }
1984 if !found_excerpt_header {
1985 offset_y -= block_offset as f32 * line_height;
1986 length += block_height as f32 * line_height;
1987 }
1988
1989 // If there is a block (e.g. diagnostic) at the end of an multibuffer excerpt,
1990 // we want to ensure that the indent guide stops before the excerpt header.
1991 let mut block_height = 0;
1992 let mut found_excerpt_header = false;
1993 for (_, block) in snapshot.blocks_in_range(row_range.end..cons_line) {
1994 if matches!(block, Block::ExcerptBoundary { .. }) {
1995 found_excerpt_header = true;
1996 }
1997 block_height += block.height();
1998 }
1999 if found_excerpt_header {
2000 length -= block_height as f32 * line_height;
2001 }
2002
2003 (offset_y, length)
2004 }
2005
2006 fn layout_breakpoints(
2007 &self,
2008 line_height: Pixels,
2009 range: Range<DisplayRow>,
2010 scroll_pixel_position: gpui::Point<Pixels>,
2011 gutter_dimensions: &GutterDimensions,
2012 gutter_hitbox: &Hitbox,
2013 display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
2014 snapshot: &EditorSnapshot,
2015 breakpoints: HashMap<DisplayRow, (Anchor, Breakpoint)>,
2016 row_infos: &[RowInfo],
2017 window: &mut Window,
2018 cx: &mut App,
2019 ) -> Vec<AnyElement> {
2020 self.editor.update(cx, |editor, cx| {
2021 breakpoints
2022 .into_iter()
2023 .filter_map(|(display_row, (text_anchor, bp))| {
2024 if row_infos
2025 .get((display_row.0.saturating_sub(range.start.0)) as usize)
2026 .is_some_and(|row_info| {
2027 row_info.expand_info.is_some()
2028 || row_info
2029 .diff_status
2030 .is_some_and(|status| status.is_deleted())
2031 })
2032 {
2033 return None;
2034 }
2035
2036 if range.start > display_row || range.end < display_row {
2037 return None;
2038 }
2039
2040 let row =
2041 MultiBufferRow(DisplayPoint::new(display_row, 0).to_point(&snapshot).row);
2042 if snapshot.is_line_folded(row) {
2043 return None;
2044 }
2045
2046 let button = editor.render_breakpoint(text_anchor, display_row, &bp, cx);
2047
2048 let button = prepaint_gutter_button(
2049 button,
2050 display_row,
2051 line_height,
2052 gutter_dimensions,
2053 scroll_pixel_position,
2054 gutter_hitbox,
2055 display_hunks,
2056 window,
2057 cx,
2058 );
2059 Some(button)
2060 })
2061 .collect_vec()
2062 })
2063 }
2064
2065 #[allow(clippy::too_many_arguments)]
2066 fn layout_run_indicators(
2067 &self,
2068 line_height: Pixels,
2069 range: Range<DisplayRow>,
2070 row_infos: &[RowInfo],
2071 scroll_pixel_position: gpui::Point<Pixels>,
2072 gutter_dimensions: &GutterDimensions,
2073 gutter_hitbox: &Hitbox,
2074 display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
2075 snapshot: &EditorSnapshot,
2076 breakpoints: &mut HashMap<DisplayRow, (Anchor, Breakpoint)>,
2077 window: &mut Window,
2078 cx: &mut App,
2079 ) -> Vec<AnyElement> {
2080 self.editor.update(cx, |editor, cx| {
2081 let active_task_indicator_row =
2082 if let Some(crate::CodeContextMenu::CodeActions(CodeActionsMenu {
2083 deployed_from_indicator,
2084 actions,
2085 ..
2086 })) = editor.context_menu.borrow().as_ref()
2087 {
2088 actions
2089 .tasks
2090 .as_ref()
2091 .map(|tasks| tasks.position.to_display_point(snapshot).row())
2092 .or(*deployed_from_indicator)
2093 } else {
2094 None
2095 };
2096
2097 let offset_range_start =
2098 snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left);
2099
2100 let offset_range_end =
2101 snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
2102
2103 editor
2104 .tasks
2105 .iter()
2106 .filter_map(|(_, tasks)| {
2107 let multibuffer_point = tasks.offset.to_point(&snapshot.buffer_snapshot);
2108 if multibuffer_point < offset_range_start
2109 || multibuffer_point > offset_range_end
2110 {
2111 return None;
2112 }
2113 let multibuffer_row = MultiBufferRow(multibuffer_point.row);
2114 let buffer_folded = snapshot
2115 .buffer_snapshot
2116 .buffer_line_for_row(multibuffer_row)
2117 .map(|(buffer_snapshot, _)| buffer_snapshot.remote_id())
2118 .map(|buffer_id| editor.is_buffer_folded(buffer_id, cx))
2119 .unwrap_or(false);
2120 if buffer_folded {
2121 return None;
2122 }
2123
2124 if snapshot.is_line_folded(multibuffer_row) {
2125 // Skip folded indicators, unless it's the starting line of a fold.
2126 if multibuffer_row
2127 .0
2128 .checked_sub(1)
2129 .map_or(false, |previous_row| {
2130 snapshot.is_line_folded(MultiBufferRow(previous_row))
2131 })
2132 {
2133 return None;
2134 }
2135 }
2136
2137 let display_row = multibuffer_point.to_display_point(snapshot).row();
2138 if row_infos
2139 .get((display_row - range.start).0 as usize)
2140 .is_some_and(|row_info| row_info.expand_info.is_some())
2141 {
2142 return None;
2143 }
2144
2145 let button = editor.render_run_indicator(
2146 &self.style,
2147 Some(display_row) == active_task_indicator_row,
2148 display_row,
2149 breakpoints.remove(&display_row),
2150 cx,
2151 );
2152
2153 let button = prepaint_gutter_button(
2154 button,
2155 display_row,
2156 line_height,
2157 gutter_dimensions,
2158 scroll_pixel_position,
2159 gutter_hitbox,
2160 display_hunks,
2161 window,
2162 cx,
2163 );
2164 Some(button)
2165 })
2166 .collect_vec()
2167 })
2168 }
2169
2170 fn layout_expand_toggles(
2171 &self,
2172 gutter_hitbox: &Hitbox,
2173 gutter_dimensions: GutterDimensions,
2174 em_width: Pixels,
2175 line_height: Pixels,
2176 scroll_position: gpui::Point<f32>,
2177 buffer_rows: &[RowInfo],
2178 window: &mut Window,
2179 cx: &mut App,
2180 ) -> Vec<Option<(AnyElement, gpui::Point<Pixels>)>> {
2181 let editor_font_size = self.style.text.font_size.to_pixels(window.rem_size()) * 1.2;
2182
2183 let scroll_top = scroll_position.y * line_height;
2184
2185 let max_line_number_length = self
2186 .editor
2187 .read(cx)
2188 .buffer()
2189 .read(cx)
2190 .snapshot(cx)
2191 .widest_line_number()
2192 .ilog10()
2193 + 1;
2194
2195 let elements = buffer_rows
2196 .into_iter()
2197 .enumerate()
2198 .map(|(ix, row_info)| {
2199 let ExpandInfo {
2200 excerpt_id,
2201 direction,
2202 } = row_info.expand_info?;
2203
2204 let icon_name = match direction {
2205 ExpandExcerptDirection::Up => IconName::ExpandUp,
2206 ExpandExcerptDirection::Down => IconName::ExpandDown,
2207 ExpandExcerptDirection::UpAndDown => IconName::ExpandVertical,
2208 };
2209
2210 let git_gutter_width = Self::gutter_strip_width(line_height);
2211 let available_width = gutter_dimensions.left_padding - git_gutter_width;
2212
2213 let editor = self.editor.clone();
2214 let is_wide = max_line_number_length >= MIN_LINE_NUMBER_DIGITS
2215 && row_info
2216 .buffer_row
2217 .is_some_and(|row| (row + 1).ilog10() + 1 == max_line_number_length)
2218 || gutter_dimensions.right_padding == px(0.);
2219
2220 let width = if is_wide {
2221 available_width - px(2.)
2222 } else {
2223 available_width + em_width - px(2.)
2224 };
2225
2226 let toggle = IconButton::new(("expand", ix), icon_name)
2227 .icon_color(Color::Custom(cx.theme().colors().editor_line_number))
2228 .selected_icon_color(Color::Custom(cx.theme().colors().editor_foreground))
2229 .icon_size(IconSize::Custom(rems(editor_font_size / window.rem_size())))
2230 .width(width.into())
2231 .on_click(move |_, window, cx| {
2232 editor.update(cx, |editor, cx| {
2233 editor.expand_excerpt(excerpt_id, direction, window, cx);
2234 });
2235 })
2236 .tooltip(Tooltip::for_action_title(
2237 "Expand Excerpt",
2238 &crate::actions::ExpandExcerpts::default(),
2239 ))
2240 .into_any_element();
2241
2242 let position = point(
2243 git_gutter_width + px(1.),
2244 ix as f32 * line_height - (scroll_top % line_height) + px(1.),
2245 );
2246 let origin = gutter_hitbox.origin + position;
2247
2248 Some((toggle, origin))
2249 })
2250 .collect();
2251
2252 elements
2253 }
2254
2255 fn layout_code_actions_indicator(
2256 &self,
2257 line_height: Pixels,
2258 newest_selection_head: DisplayPoint,
2259 scroll_pixel_position: gpui::Point<Pixels>,
2260 gutter_dimensions: &GutterDimensions,
2261 gutter_hitbox: &Hitbox,
2262 breakpoint_points: &mut HashMap<DisplayRow, (Anchor, Breakpoint)>,
2263 display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
2264 window: &mut Window,
2265 cx: &mut App,
2266 ) -> Option<AnyElement> {
2267 let mut active = false;
2268 let mut button = None;
2269 let row = newest_selection_head.row();
2270 self.editor.update(cx, |editor, cx| {
2271 if let Some(crate::CodeContextMenu::CodeActions(CodeActionsMenu {
2272 deployed_from_indicator,
2273 ..
2274 })) = editor.context_menu.borrow().as_ref()
2275 {
2276 active = deployed_from_indicator.map_or(true, |indicator_row| indicator_row == row);
2277 };
2278
2279 let breakpoint = breakpoint_points.get(&row);
2280 button = editor.render_code_actions_indicator(&self.style, row, active, breakpoint, cx);
2281 });
2282
2283 let button = button?;
2284 breakpoint_points.remove(&row);
2285
2286 let button = prepaint_gutter_button(
2287 button,
2288 row,
2289 line_height,
2290 gutter_dimensions,
2291 scroll_pixel_position,
2292 gutter_hitbox,
2293 display_hunks,
2294 window,
2295 cx,
2296 );
2297
2298 Some(button)
2299 }
2300
2301 fn get_participant_color(participant_index: Option<ParticipantIndex>, cx: &App) -> PlayerColor {
2302 if let Some(index) = participant_index {
2303 cx.theme().players().color_for_participant(index.0)
2304 } else {
2305 cx.theme().players().absent()
2306 }
2307 }
2308
2309 fn calculate_relative_line_numbers(
2310 &self,
2311 snapshot: &EditorSnapshot,
2312 rows: &Range<DisplayRow>,
2313 relative_to: Option<DisplayRow>,
2314 ) -> HashMap<DisplayRow, DisplayRowDelta> {
2315 let mut relative_rows: HashMap<DisplayRow, DisplayRowDelta> = Default::default();
2316 let Some(relative_to) = relative_to else {
2317 return relative_rows;
2318 };
2319
2320 let start = rows.start.min(relative_to);
2321 let end = rows.end.max(relative_to);
2322
2323 let buffer_rows = snapshot
2324 .row_infos(start)
2325 .take(1 + end.minus(start) as usize)
2326 .collect::<Vec<_>>();
2327
2328 let head_idx = relative_to.minus(start);
2329 let mut delta = 1;
2330 let mut i = head_idx + 1;
2331 while i < buffer_rows.len() as u32 {
2332 if buffer_rows[i as usize].buffer_row.is_some() {
2333 if rows.contains(&DisplayRow(i + start.0)) {
2334 relative_rows.insert(DisplayRow(i + start.0), delta);
2335 }
2336 delta += 1;
2337 }
2338 i += 1;
2339 }
2340 delta = 1;
2341 i = head_idx.min(buffer_rows.len() as u32 - 1);
2342 while i > 0 && buffer_rows[i as usize].buffer_row.is_none() {
2343 i -= 1;
2344 }
2345
2346 while i > 0 {
2347 i -= 1;
2348 if buffer_rows[i as usize].buffer_row.is_some() {
2349 if rows.contains(&DisplayRow(i + start.0)) {
2350 relative_rows.insert(DisplayRow(i + start.0), delta);
2351 }
2352 delta += 1;
2353 }
2354 }
2355
2356 relative_rows
2357 }
2358
2359 fn layout_line_numbers(
2360 &self,
2361 gutter_hitbox: Option<&Hitbox>,
2362 gutter_dimensions: GutterDimensions,
2363 line_height: Pixels,
2364 scroll_position: gpui::Point<f32>,
2365 rows: Range<DisplayRow>,
2366 buffer_rows: &[RowInfo],
2367 active_rows: &BTreeMap<DisplayRow, LineHighlightSpec>,
2368 newest_selection_head: Option<DisplayPoint>,
2369 snapshot: &EditorSnapshot,
2370 window: &mut Window,
2371 cx: &mut App,
2372 ) -> Arc<HashMap<MultiBufferRow, LineNumberLayout>> {
2373 let include_line_numbers = snapshot.show_line_numbers.unwrap_or_else(|| {
2374 EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode.is_full()
2375 });
2376 if !include_line_numbers {
2377 return Arc::default();
2378 }
2379
2380 let (newest_selection_head, is_relative) = self.editor.update(cx, |editor, cx| {
2381 let newest_selection_head = newest_selection_head.unwrap_or_else(|| {
2382 let newest = editor.selections.newest::<Point>(cx);
2383 SelectionLayout::new(
2384 newest,
2385 editor.selections.line_mode,
2386 editor.cursor_shape,
2387 &snapshot.display_snapshot,
2388 true,
2389 true,
2390 None,
2391 )
2392 .head
2393 });
2394 let is_relative = editor.should_use_relative_line_numbers(cx);
2395 (newest_selection_head, is_relative)
2396 });
2397
2398 let relative_to = if is_relative {
2399 Some(newest_selection_head.row())
2400 } else {
2401 None
2402 };
2403 let relative_rows = self.calculate_relative_line_numbers(snapshot, &rows, relative_to);
2404 let mut line_number = String::new();
2405 let line_numbers = buffer_rows
2406 .into_iter()
2407 .enumerate()
2408 .flat_map(|(ix, row_info)| {
2409 let display_row = DisplayRow(rows.start.0 + ix as u32);
2410 line_number.clear();
2411 let non_relative_number = row_info.buffer_row? + 1;
2412 let number = relative_rows
2413 .get(&display_row)
2414 .unwrap_or(&non_relative_number);
2415 write!(&mut line_number, "{number}").unwrap();
2416 if row_info
2417 .diff_status
2418 .is_some_and(|status| status.is_deleted())
2419 {
2420 return None;
2421 }
2422
2423 let color = active_rows
2424 .get(&display_row)
2425 .map(|spec| {
2426 if spec.breakpoint {
2427 cx.theme().colors().debugger_accent
2428 } else {
2429 cx.theme().colors().editor_active_line_number
2430 }
2431 })
2432 .unwrap_or_else(|| cx.theme().colors().editor_line_number);
2433 let shaped_line = self
2434 .shape_line_number(SharedString::from(&line_number), color, window)
2435 .log_err()?;
2436 let scroll_top = scroll_position.y * line_height;
2437 let line_origin = gutter_hitbox.map(|hitbox| {
2438 hitbox.origin
2439 + point(
2440 hitbox.size.width - shaped_line.width - gutter_dimensions.right_padding,
2441 ix as f32 * line_height - (scroll_top % line_height),
2442 )
2443 });
2444
2445 #[cfg(not(test))]
2446 let hitbox = line_origin.map(|line_origin| {
2447 window.insert_hitbox(
2448 Bounds::new(line_origin, size(shaped_line.width, line_height)),
2449 false,
2450 )
2451 });
2452 #[cfg(test)]
2453 let hitbox = {
2454 let _ = line_origin;
2455 None
2456 };
2457
2458 let multi_buffer_row = DisplayPoint::new(display_row, 0).to_point(snapshot).row;
2459 let multi_buffer_row = MultiBufferRow(multi_buffer_row);
2460 let line_number = LineNumberLayout {
2461 shaped_line,
2462 hitbox,
2463 };
2464 Some((multi_buffer_row, line_number))
2465 })
2466 .collect();
2467 Arc::new(line_numbers)
2468 }
2469
2470 fn layout_crease_toggles(
2471 &self,
2472 rows: Range<DisplayRow>,
2473 row_infos: &[RowInfo],
2474 active_rows: &BTreeMap<DisplayRow, LineHighlightSpec>,
2475 snapshot: &EditorSnapshot,
2476 window: &mut Window,
2477 cx: &mut App,
2478 ) -> Vec<Option<AnyElement>> {
2479 let include_fold_statuses = EditorSettings::get_global(cx).gutter.folds
2480 && snapshot.mode.is_full()
2481 && self.editor.read(cx).is_singleton(cx);
2482 if include_fold_statuses {
2483 row_infos
2484 .into_iter()
2485 .enumerate()
2486 .map(|(ix, info)| {
2487 if info.expand_info.is_some() {
2488 return None;
2489 }
2490 let row = info.multibuffer_row?;
2491 let display_row = DisplayRow(rows.start.0 + ix as u32);
2492 let active = active_rows.contains_key(&display_row);
2493
2494 snapshot.render_crease_toggle(row, active, self.editor.clone(), window, cx)
2495 })
2496 .collect()
2497 } else {
2498 Vec::new()
2499 }
2500 }
2501
2502 fn layout_crease_trailers(
2503 &self,
2504 buffer_rows: impl IntoIterator<Item = RowInfo>,
2505 snapshot: &EditorSnapshot,
2506 window: &mut Window,
2507 cx: &mut App,
2508 ) -> Vec<Option<AnyElement>> {
2509 buffer_rows
2510 .into_iter()
2511 .map(|row_info| {
2512 if row_info.expand_info.is_some() {
2513 return None;
2514 }
2515 if let Some(row) = row_info.multibuffer_row {
2516 snapshot.render_crease_trailer(row, window, cx)
2517 } else {
2518 None
2519 }
2520 })
2521 .collect()
2522 }
2523
2524 fn layout_lines(
2525 rows: Range<DisplayRow>,
2526 snapshot: &EditorSnapshot,
2527 style: &EditorStyle,
2528 editor_width: Pixels,
2529 is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
2530 window: &mut Window,
2531 cx: &mut App,
2532 ) -> Vec<LineWithInvisibles> {
2533 if rows.start >= rows.end {
2534 return Vec::new();
2535 }
2536
2537 // Show the placeholder when the editor is empty
2538 if snapshot.is_empty() {
2539 let font_size = style.text.font_size.to_pixels(window.rem_size());
2540 let placeholder_color = cx.theme().colors().text_placeholder;
2541 let placeholder_text = snapshot.placeholder_text();
2542
2543 let placeholder_lines = placeholder_text
2544 .as_ref()
2545 .map_or("", AsRef::as_ref)
2546 .split('\n')
2547 .skip(rows.start.0 as usize)
2548 .chain(iter::repeat(""))
2549 .take(rows.len());
2550 placeholder_lines
2551 .filter_map(move |line| {
2552 let run = TextRun {
2553 len: line.len(),
2554 font: style.text.font(),
2555 color: placeholder_color,
2556 background_color: None,
2557 underline: Default::default(),
2558 strikethrough: None,
2559 };
2560 window
2561 .text_system()
2562 .shape_line(line.to_string().into(), font_size, &[run])
2563 .log_err()
2564 })
2565 .map(|line| LineWithInvisibles {
2566 width: line.width,
2567 len: line.len,
2568 fragments: smallvec![LineFragment::Text(line)],
2569 invisibles: Vec::new(),
2570 font_size,
2571 })
2572 .collect()
2573 } else {
2574 let chunks = snapshot.highlighted_chunks(rows.clone(), true, style);
2575 LineWithInvisibles::from_chunks(
2576 chunks,
2577 &style,
2578 MAX_LINE_LEN,
2579 rows.len(),
2580 snapshot.mode,
2581 editor_width,
2582 is_row_soft_wrapped,
2583 window,
2584 cx,
2585 )
2586 }
2587 }
2588
2589 fn prepaint_lines(
2590 &self,
2591 start_row: DisplayRow,
2592 line_layouts: &mut [LineWithInvisibles],
2593 line_height: Pixels,
2594 scroll_pixel_position: gpui::Point<Pixels>,
2595 content_origin: gpui::Point<Pixels>,
2596 window: &mut Window,
2597 cx: &mut App,
2598 ) -> SmallVec<[AnyElement; 1]> {
2599 let mut line_elements = SmallVec::new();
2600 for (ix, line) in line_layouts.iter_mut().enumerate() {
2601 let row = start_row + DisplayRow(ix as u32);
2602 line.prepaint(
2603 line_height,
2604 scroll_pixel_position,
2605 row,
2606 content_origin,
2607 &mut line_elements,
2608 window,
2609 cx,
2610 );
2611 }
2612 line_elements
2613 }
2614
2615 fn render_block(
2616 &self,
2617 block: &Block,
2618 available_width: AvailableSpace,
2619 block_id: BlockId,
2620 block_row_start: DisplayRow,
2621 snapshot: &EditorSnapshot,
2622 text_x: Pixels,
2623 rows: &Range<DisplayRow>,
2624 line_layouts: &[LineWithInvisibles],
2625 gutter_dimensions: &GutterDimensions,
2626 line_height: Pixels,
2627 em_width: Pixels,
2628 text_hitbox: &Hitbox,
2629 editor_width: Pixels,
2630 scroll_width: &mut Pixels,
2631 resized_blocks: &mut HashMap<CustomBlockId, u32>,
2632 row_block_types: &mut HashMap<DisplayRow, bool>,
2633 selections: &[Selection<Point>],
2634 selected_buffer_ids: &Vec<BufferId>,
2635 is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
2636 sticky_header_excerpt_id: Option<ExcerptId>,
2637 window: &mut Window,
2638 cx: &mut App,
2639 ) -> (AnyElement, Size<Pixels>, DisplayRow, Pixels) {
2640 let mut x_position = None;
2641 let mut element = match block {
2642 Block::Custom(block) => {
2643 let block_start = block.start().to_point(&snapshot.buffer_snapshot);
2644 let block_end = block.end().to_point(&snapshot.buffer_snapshot);
2645 let align_to = block_start.to_display_point(snapshot);
2646 let x_and_width = |layout: &LineWithInvisibles| {
2647 Some((
2648 text_x + layout.x_for_index(align_to.column() as usize),
2649 text_x + layout.width,
2650 ))
2651 };
2652 x_position = if rows.contains(&align_to.row()) {
2653 x_and_width(&line_layouts[align_to.row().minus(rows.start) as usize])
2654 } else {
2655 x_and_width(&layout_line(
2656 align_to.row(),
2657 snapshot,
2658 &self.style,
2659 editor_width,
2660 is_row_soft_wrapped,
2661 window,
2662 cx,
2663 ))
2664 };
2665
2666 let anchor_x = x_position.unwrap().0;
2667
2668 let selected = selections
2669 .binary_search_by(|selection| {
2670 if selection.end <= block_start {
2671 Ordering::Less
2672 } else if selection.start >= block_end {
2673 Ordering::Greater
2674 } else {
2675 Ordering::Equal
2676 }
2677 })
2678 .is_ok();
2679
2680 div()
2681 .size_full()
2682 .child(block.render(&mut BlockContext {
2683 window,
2684 app: cx,
2685 anchor_x,
2686 gutter_dimensions,
2687 line_height,
2688 em_width,
2689 block_id,
2690 selected,
2691 max_width: text_hitbox.size.width.max(*scroll_width),
2692 editor_style: &self.style,
2693 }))
2694 .into_any()
2695 }
2696
2697 Block::FoldedBuffer {
2698 first_excerpt,
2699 height,
2700 ..
2701 } => {
2702 let selected = selected_buffer_ids.contains(&first_excerpt.buffer_id);
2703 let result = v_flex().id(block_id).w_full();
2704
2705 let jump_data = header_jump_data(snapshot, block_row_start, *height, first_excerpt);
2706 result
2707 .child(self.render_buffer_header(
2708 first_excerpt,
2709 true,
2710 selected,
2711 false,
2712 jump_data,
2713 window,
2714 cx,
2715 ))
2716 .into_any_element()
2717 }
2718
2719 Block::ExcerptBoundary {
2720 excerpt,
2721 height,
2722 starts_new_buffer,
2723 ..
2724 } => {
2725 let color = cx.theme().colors().clone();
2726 let mut result = v_flex().id(block_id).w_full();
2727
2728 let jump_data = header_jump_data(snapshot, block_row_start, *height, excerpt);
2729
2730 if *starts_new_buffer {
2731 if sticky_header_excerpt_id != Some(excerpt.id) {
2732 let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
2733
2734 result = result.child(self.render_buffer_header(
2735 excerpt, false, selected, false, jump_data, window, cx,
2736 ));
2737 } else {
2738 result =
2739 result.child(div().h(FILE_HEADER_HEIGHT as f32 * window.line_height()));
2740 }
2741 } else {
2742 result = result.child(
2743 h_flex().relative().child(
2744 div()
2745 .top(line_height / 2.)
2746 .absolute()
2747 .w_full()
2748 .h_px()
2749 .bg(color.border_variant),
2750 ),
2751 );
2752 };
2753
2754 result.into_any()
2755 }
2756 };
2757
2758 // Discover the element's content height, then round up to the nearest multiple of line height.
2759 let preliminary_size = element.layout_as_root(
2760 size(available_width, AvailableSpace::MinContent),
2761 window,
2762 cx,
2763 );
2764 let quantized_height = (preliminary_size.height / line_height).ceil() * line_height;
2765 let final_size = if preliminary_size.height == quantized_height {
2766 preliminary_size
2767 } else {
2768 element.layout_as_root(size(available_width, quantized_height.into()), window, cx)
2769 };
2770
2771 let mut row = block_row_start;
2772 let mut x_offset = px(0.);
2773 let mut is_block = true;
2774
2775 if let BlockId::Custom(custom_block_id) = block_id {
2776 if block.has_height() {
2777 let mut element_height_in_lines =
2778 ((final_size.height / line_height).ceil() as u32).max(1);
2779
2780 if block.place_near() && element_height_in_lines == 1 {
2781 if let Some((x_target, line_width)) = x_position {
2782 let margin = em_width * 2;
2783 if line_width + final_size.width + margin
2784 < editor_width + gutter_dimensions.full_width()
2785 && !row_block_types.contains_key(&(row - 1))
2786 {
2787 x_offset = line_width + margin;
2788 row = row - 1;
2789 is_block = false;
2790 element_height_in_lines = 0;
2791 } else {
2792 let max_offset =
2793 editor_width + gutter_dimensions.full_width() - final_size.width;
2794 let min_offset = (x_target + em_width - final_size.width)
2795 .max(gutter_dimensions.full_width());
2796 x_offset = x_target.min(max_offset).max(min_offset);
2797 }
2798 }
2799 };
2800 if element_height_in_lines != block.height() {
2801 resized_blocks.insert(custom_block_id, element_height_in_lines);
2802 }
2803 }
2804 }
2805 row_block_types.insert(row, is_block);
2806
2807 (element, final_size, row, x_offset)
2808 }
2809
2810 fn render_buffer_header(
2811 &self,
2812 for_excerpt: &ExcerptInfo,
2813 is_folded: bool,
2814 is_selected: bool,
2815 is_sticky: bool,
2816 jump_data: JumpData,
2817 window: &mut Window,
2818 cx: &mut App,
2819 ) -> Div {
2820 let editor = self.editor.read(cx);
2821 let file_status = editor
2822 .buffer
2823 .read(cx)
2824 .all_diff_hunks_expanded()
2825 .then(|| {
2826 editor
2827 .project
2828 .as_ref()?
2829 .read(cx)
2830 .status_for_buffer_id(for_excerpt.buffer_id, cx)
2831 })
2832 .flatten();
2833
2834 let include_root = editor
2835 .project
2836 .as_ref()
2837 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
2838 .unwrap_or_default();
2839 let can_open_excerpts = Editor::can_open_excerpts_in_file(for_excerpt.buffer.file());
2840 let path = for_excerpt.buffer.resolve_file_path(cx, include_root);
2841 let filename = path
2842 .as_ref()
2843 .and_then(|path| Some(path.file_name()?.to_string_lossy().to_string()));
2844 let parent_path = path.as_ref().and_then(|path| {
2845 Some(path.parent()?.to_string_lossy().to_string() + std::path::MAIN_SEPARATOR_STR)
2846 });
2847 let focus_handle = editor.focus_handle(cx);
2848 let colors = cx.theme().colors();
2849
2850 div()
2851 .p_1()
2852 .w_full()
2853 .h(FILE_HEADER_HEIGHT as f32 * window.line_height())
2854 .child(
2855 h_flex()
2856 .size_full()
2857 .gap_2()
2858 .flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
2859 .pl_0p5()
2860 .pr_5()
2861 .rounded_sm()
2862 .when(is_sticky, |el| el.shadow_md())
2863 .border_1()
2864 .map(|div| {
2865 let border_color = if is_selected
2866 && is_folded
2867 && focus_handle.contains_focused(window, cx)
2868 {
2869 colors.border_focused
2870 } else {
2871 colors.border
2872 };
2873 div.border_color(border_color)
2874 })
2875 .bg(colors.editor_subheader_background)
2876 .hover(|style| style.bg(colors.element_hover))
2877 .map(|header| {
2878 let editor = self.editor.clone();
2879 let buffer_id = for_excerpt.buffer_id;
2880 let toggle_chevron_icon =
2881 FileIcons::get_chevron_icon(!is_folded, cx).map(Icon::from_path);
2882 header.child(
2883 div()
2884 .hover(|style| style.bg(colors.element_selected))
2885 .rounded_xs()
2886 .child(
2887 ButtonLike::new("toggle-buffer-fold")
2888 .style(ui::ButtonStyle::Transparent)
2889 .height(px(28.).into())
2890 .width(px(28.).into())
2891 .children(toggle_chevron_icon)
2892 .tooltip({
2893 let focus_handle = focus_handle.clone();
2894 move |window, cx| {
2895 Tooltip::for_action_in(
2896 "Toggle Excerpt Fold",
2897 &ToggleFold,
2898 &focus_handle,
2899 window,
2900 cx,
2901 )
2902 }
2903 })
2904 .on_click(move |_, _, cx| {
2905 if is_folded {
2906 editor.update(cx, |editor, cx| {
2907 editor.unfold_buffer(buffer_id, cx);
2908 });
2909 } else {
2910 editor.update(cx, |editor, cx| {
2911 editor.fold_buffer(buffer_id, cx);
2912 });
2913 }
2914 }),
2915 ),
2916 )
2917 })
2918 .children(
2919 editor
2920 .addons
2921 .values()
2922 .filter_map(|addon| {
2923 addon.render_buffer_header_controls(for_excerpt, window, cx)
2924 })
2925 .take(1),
2926 )
2927 .child(
2928 h_flex()
2929 .cursor_pointer()
2930 .id("path header block")
2931 .size_full()
2932 .justify_between()
2933 .child(
2934 h_flex()
2935 .gap_2()
2936 .child(
2937 Label::new(
2938 filename
2939 .map(SharedString::from)
2940 .unwrap_or_else(|| "untitled".into()),
2941 )
2942 .single_line()
2943 .when_some(
2944 file_status,
2945 |el, status| {
2946 el.color(if status.is_conflicted() {
2947 Color::Conflict
2948 } else if status.is_modified() {
2949 Color::Modified
2950 } else if status.is_deleted() {
2951 Color::Disabled
2952 } else {
2953 Color::Created
2954 })
2955 .when(status.is_deleted(), |el| el.strikethrough())
2956 },
2957 ),
2958 )
2959 .when_some(parent_path, |then, path| {
2960 then.child(div().child(path).text_color(
2961 if file_status.is_some_and(FileStatus::is_deleted) {
2962 colors.text_disabled
2963 } else {
2964 colors.text_muted
2965 },
2966 ))
2967 }),
2968 )
2969 .when(can_open_excerpts && is_selected && path.is_some(), |el| {
2970 el.child(
2971 h_flex()
2972 .id("jump-to-file-button")
2973 .gap_2p5()
2974 .child(Label::new("Jump To File"))
2975 .children(
2976 KeyBinding::for_action_in(
2977 &OpenExcerpts,
2978 &focus_handle,
2979 window,
2980 cx,
2981 )
2982 .map(|binding| binding.into_any_element()),
2983 ),
2984 )
2985 })
2986 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2987 .on_click(window.listener_for(&self.editor, {
2988 move |editor, e: &ClickEvent, window, cx| {
2989 editor.open_excerpts_common(
2990 Some(jump_data.clone()),
2991 e.down.modifiers.secondary(),
2992 window,
2993 cx,
2994 );
2995 }
2996 })),
2997 ),
2998 )
2999 }
3000
3001 fn render_blocks(
3002 &self,
3003 rows: Range<DisplayRow>,
3004 snapshot: &EditorSnapshot,
3005 hitbox: &Hitbox,
3006 text_hitbox: &Hitbox,
3007 editor_width: Pixels,
3008 scroll_width: &mut Pixels,
3009 gutter_dimensions: &GutterDimensions,
3010 em_width: Pixels,
3011 text_x: Pixels,
3012 line_height: Pixels,
3013 line_layouts: &mut [LineWithInvisibles],
3014 selections: &[Selection<Point>],
3015 selected_buffer_ids: &Vec<BufferId>,
3016 is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
3017 sticky_header_excerpt_id: Option<ExcerptId>,
3018 window: &mut Window,
3019 cx: &mut App,
3020 ) -> Result<(Vec<BlockLayout>, HashMap<DisplayRow, bool>), HashMap<CustomBlockId, u32>> {
3021 let (fixed_blocks, non_fixed_blocks) = snapshot
3022 .blocks_in_range(rows.clone())
3023 .partition::<Vec<_>, _>(|(_, block)| block.style() == BlockStyle::Fixed);
3024
3025 let mut focused_block = self
3026 .editor
3027 .update(cx, |editor, _| editor.take_focused_block());
3028 let mut fixed_block_max_width = Pixels::ZERO;
3029 let mut blocks = Vec::new();
3030 let mut resized_blocks = HashMap::default();
3031 let mut row_block_types = HashMap::default();
3032
3033 for (row, block) in fixed_blocks {
3034 let block_id = block.id();
3035
3036 if focused_block.as_ref().map_or(false, |b| b.id == block_id) {
3037 focused_block = None;
3038 }
3039
3040 let (element, element_size, row, x_offset) = self.render_block(
3041 block,
3042 AvailableSpace::MinContent,
3043 block_id,
3044 row,
3045 snapshot,
3046 text_x,
3047 &rows,
3048 line_layouts,
3049 gutter_dimensions,
3050 line_height,
3051 em_width,
3052 text_hitbox,
3053 editor_width,
3054 scroll_width,
3055 &mut resized_blocks,
3056 &mut row_block_types,
3057 selections,
3058 selected_buffer_ids,
3059 is_row_soft_wrapped,
3060 sticky_header_excerpt_id,
3061 window,
3062 cx,
3063 );
3064
3065 fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
3066 blocks.push(BlockLayout {
3067 id: block_id,
3068 x_offset,
3069 row: Some(row),
3070 element,
3071 available_space: size(AvailableSpace::MinContent, element_size.height.into()),
3072 style: BlockStyle::Fixed,
3073 overlaps_gutter: true,
3074 is_buffer_header: block.is_buffer_header(),
3075 });
3076 }
3077
3078 for (row, block) in non_fixed_blocks {
3079 let style = block.style();
3080 let width = match (style, block.place_near()) {
3081 (_, true) => AvailableSpace::MinContent,
3082 (BlockStyle::Sticky, _) => hitbox.size.width.into(),
3083 (BlockStyle::Flex, _) => hitbox
3084 .size
3085 .width
3086 .max(fixed_block_max_width)
3087 .max(gutter_dimensions.width + *scroll_width)
3088 .into(),
3089 (BlockStyle::Fixed, _) => unreachable!(),
3090 };
3091 let block_id = block.id();
3092
3093 if focused_block.as_ref().map_or(false, |b| b.id == block_id) {
3094 focused_block = None;
3095 }
3096
3097 let (element, element_size, row, x_offset) = self.render_block(
3098 block,
3099 width,
3100 block_id,
3101 row,
3102 snapshot,
3103 text_x,
3104 &rows,
3105 line_layouts,
3106 gutter_dimensions,
3107 line_height,
3108 em_width,
3109 text_hitbox,
3110 editor_width,
3111 scroll_width,
3112 &mut resized_blocks,
3113 &mut row_block_types,
3114 selections,
3115 selected_buffer_ids,
3116 is_row_soft_wrapped,
3117 sticky_header_excerpt_id,
3118 window,
3119 cx,
3120 );
3121
3122 blocks.push(BlockLayout {
3123 id: block_id,
3124 x_offset,
3125 row: Some(row),
3126 element,
3127 available_space: size(width, element_size.height.into()),
3128 style,
3129 overlaps_gutter: !block.place_near(),
3130 is_buffer_header: block.is_buffer_header(),
3131 });
3132 }
3133
3134 if let Some(focused_block) = focused_block {
3135 if let Some(focus_handle) = focused_block.focus_handle.upgrade() {
3136 if focus_handle.is_focused(window) {
3137 if let Some(block) = snapshot.block_for_id(focused_block.id) {
3138 let style = block.style();
3139 let width = match style {
3140 BlockStyle::Fixed => AvailableSpace::MinContent,
3141 BlockStyle::Flex => AvailableSpace::Definite(
3142 hitbox
3143 .size
3144 .width
3145 .max(fixed_block_max_width)
3146 .max(gutter_dimensions.width + *scroll_width),
3147 ),
3148 BlockStyle::Sticky => AvailableSpace::Definite(hitbox.size.width),
3149 };
3150
3151 let (element, element_size, _, x_offset) = self.render_block(
3152 &block,
3153 width,
3154 focused_block.id,
3155 rows.end,
3156 snapshot,
3157 text_x,
3158 &rows,
3159 line_layouts,
3160 gutter_dimensions,
3161 line_height,
3162 em_width,
3163 text_hitbox,
3164 editor_width,
3165 scroll_width,
3166 &mut resized_blocks,
3167 &mut row_block_types,
3168 selections,
3169 selected_buffer_ids,
3170 is_row_soft_wrapped,
3171 sticky_header_excerpt_id,
3172 window,
3173 cx,
3174 );
3175
3176 blocks.push(BlockLayout {
3177 id: block.id(),
3178 x_offset,
3179 row: None,
3180 element,
3181 available_space: size(width, element_size.height.into()),
3182 style,
3183 overlaps_gutter: true,
3184 is_buffer_header: block.is_buffer_header(),
3185 });
3186 }
3187 }
3188 }
3189 }
3190
3191 if resized_blocks.is_empty() {
3192 *scroll_width = (*scroll_width).max(fixed_block_max_width - gutter_dimensions.width);
3193 Ok((blocks, row_block_types))
3194 } else {
3195 Err(resized_blocks)
3196 }
3197 }
3198
3199 fn layout_blocks(
3200 &self,
3201 blocks: &mut Vec<BlockLayout>,
3202 hitbox: &Hitbox,
3203 line_height: Pixels,
3204 scroll_pixel_position: gpui::Point<Pixels>,
3205 window: &mut Window,
3206 cx: &mut App,
3207 ) {
3208 for block in blocks {
3209 let mut origin = if let Some(row) = block.row {
3210 hitbox.origin
3211 + point(
3212 block.x_offset,
3213 row.as_f32() * line_height - scroll_pixel_position.y,
3214 )
3215 } else {
3216 // Position the block outside the visible area
3217 hitbox.origin + point(Pixels::ZERO, hitbox.size.height)
3218 };
3219
3220 if !matches!(block.style, BlockStyle::Sticky) {
3221 origin += point(-scroll_pixel_position.x, Pixels::ZERO);
3222 }
3223
3224 let focus_handle =
3225 block
3226 .element
3227 .prepaint_as_root(origin, block.available_space, window, cx);
3228
3229 if let Some(focus_handle) = focus_handle {
3230 self.editor.update(cx, |editor, _cx| {
3231 editor.set_focused_block(FocusedBlock {
3232 id: block.id,
3233 focus_handle: focus_handle.downgrade(),
3234 });
3235 });
3236 }
3237 }
3238 }
3239
3240 fn layout_sticky_buffer_header(
3241 &self,
3242 StickyHeaderExcerpt { excerpt }: StickyHeaderExcerpt<'_>,
3243 scroll_position: f32,
3244 line_height: Pixels,
3245 snapshot: &EditorSnapshot,
3246 hitbox: &Hitbox,
3247 selected_buffer_ids: &Vec<BufferId>,
3248 blocks: &[BlockLayout],
3249 window: &mut Window,
3250 cx: &mut App,
3251 ) -> AnyElement {
3252 let jump_data = header_jump_data(
3253 snapshot,
3254 DisplayRow(scroll_position as u32),
3255 FILE_HEADER_HEIGHT + MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3256 excerpt,
3257 );
3258
3259 let editor_bg_color = cx.theme().colors().editor_background;
3260
3261 let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
3262
3263 let mut header = v_flex()
3264 .relative()
3265 .child(
3266 div()
3267 .w(hitbox.bounds.size.width)
3268 .h(FILE_HEADER_HEIGHT as f32 * line_height)
3269 .bg(linear_gradient(
3270 0.,
3271 linear_color_stop(editor_bg_color.opacity(0.), 0.),
3272 linear_color_stop(editor_bg_color, 0.6),
3273 ))
3274 .absolute()
3275 .top_0(),
3276 )
3277 .child(
3278 self.render_buffer_header(excerpt, false, selected, true, jump_data, window, cx)
3279 .into_any_element(),
3280 )
3281 .into_any_element();
3282
3283 let mut origin = hitbox.origin;
3284 // Move floating header up to avoid colliding with the next buffer header.
3285 for block in blocks.iter() {
3286 if !block.is_buffer_header {
3287 continue;
3288 }
3289
3290 let Some(display_row) = block.row.filter(|row| row.0 > scroll_position as u32) else {
3291 continue;
3292 };
3293
3294 let max_row = display_row.0.saturating_sub(FILE_HEADER_HEIGHT);
3295 let offset = scroll_position - max_row as f32;
3296
3297 if offset > 0.0 {
3298 origin.y -= offset * line_height;
3299 }
3300 break;
3301 }
3302
3303 let size = size(
3304 AvailableSpace::Definite(hitbox.size.width),
3305 AvailableSpace::MinContent,
3306 );
3307
3308 header.prepaint_as_root(origin, size, window, cx);
3309
3310 header
3311 }
3312
3313 fn layout_cursor_popovers(
3314 &self,
3315 line_height: Pixels,
3316 text_hitbox: &Hitbox,
3317 content_origin: gpui::Point<Pixels>,
3318 start_row: DisplayRow,
3319 scroll_pixel_position: gpui::Point<Pixels>,
3320 line_layouts: &[LineWithInvisibles],
3321 cursor: DisplayPoint,
3322 cursor_point: Point,
3323 style: &EditorStyle,
3324 window: &mut Window,
3325 cx: &mut App,
3326 ) {
3327 let mut min_menu_height = Pixels::ZERO;
3328 let mut max_menu_height = Pixels::ZERO;
3329 let mut height_above_menu = Pixels::ZERO;
3330 let height_below_menu = Pixels::ZERO;
3331 let mut edit_prediction_popover_visible = false;
3332 let mut context_menu_visible = false;
3333 let context_menu_placement;
3334
3335 {
3336 let editor = self.editor.read(cx);
3337 if editor
3338 .edit_prediction_visible_in_cursor_popover(editor.has_active_inline_completion())
3339 {
3340 height_above_menu +=
3341 editor.edit_prediction_cursor_popover_height() + POPOVER_Y_PADDING;
3342 edit_prediction_popover_visible = true;
3343 }
3344
3345 if editor.context_menu_visible() {
3346 if let Some(crate::ContextMenuOrigin::Cursor) = editor.context_menu_origin() {
3347 let (min_height_in_lines, max_height_in_lines) = editor
3348 .context_menu_options
3349 .as_ref()
3350 .map_or((3, 12), |options| {
3351 (options.min_entries_visible, options.max_entries_visible)
3352 });
3353
3354 min_menu_height += line_height * min_height_in_lines as f32 + POPOVER_Y_PADDING;
3355 max_menu_height += line_height * max_height_in_lines as f32 + POPOVER_Y_PADDING;
3356 context_menu_visible = true;
3357 }
3358 }
3359 context_menu_placement = editor
3360 .context_menu_options
3361 .as_ref()
3362 .and_then(|options| options.placement.clone());
3363 }
3364
3365 let visible = edit_prediction_popover_visible || context_menu_visible;
3366 if !visible {
3367 return;
3368 }
3369
3370 let cursor_row_layout = &line_layouts[cursor.row().minus(start_row) as usize];
3371 let target_position = content_origin
3372 + gpui::Point {
3373 x: cmp::max(
3374 px(0.),
3375 cursor_row_layout.x_for_index(cursor.column() as usize)
3376 - scroll_pixel_position.x,
3377 ),
3378 y: cmp::max(
3379 px(0.),
3380 cursor.row().next_row().as_f32() * line_height - scroll_pixel_position.y,
3381 ),
3382 };
3383
3384 let viewport_bounds =
3385 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
3386 right: -Self::SCROLLBAR_WIDTH - MENU_GAP,
3387 ..Default::default()
3388 });
3389
3390 let min_height = height_above_menu + min_menu_height + height_below_menu;
3391 let max_height = height_above_menu + max_menu_height + height_below_menu;
3392 let Some((laid_out_popovers, y_flipped)) = self.layout_popovers_above_or_below_line(
3393 target_position,
3394 line_height,
3395 min_height,
3396 max_height,
3397 context_menu_placement,
3398 text_hitbox,
3399 viewport_bounds,
3400 window,
3401 cx,
3402 |height, max_width_for_stable_x, y_flipped, window, cx| {
3403 // First layout the menu to get its size - others can be at least this wide.
3404 let context_menu = if context_menu_visible {
3405 let menu_height = if y_flipped {
3406 height - height_below_menu
3407 } else {
3408 height - height_above_menu
3409 };
3410 let mut element = self
3411 .render_context_menu(line_height, menu_height, window, cx)
3412 .expect("Visible context menu should always render.");
3413 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
3414 Some((CursorPopoverType::CodeContextMenu, element, size))
3415 } else {
3416 None
3417 };
3418 let min_width = context_menu
3419 .as_ref()
3420 .map_or(px(0.), |(_, _, size)| size.width);
3421 let max_width = max_width_for_stable_x.max(
3422 context_menu
3423 .as_ref()
3424 .map_or(px(0.), |(_, _, size)| size.width),
3425 );
3426
3427 let edit_prediction = if edit_prediction_popover_visible {
3428 self.editor.update(cx, move |editor, cx| {
3429 let accept_binding = editor.accept_edit_prediction_keybind(window, cx);
3430 let mut element = editor.render_edit_prediction_cursor_popover(
3431 min_width,
3432 max_width,
3433 cursor_point,
3434 style,
3435 accept_binding.keystroke(),
3436 window,
3437 cx,
3438 )?;
3439 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
3440 Some((CursorPopoverType::EditPrediction, element, size))
3441 })
3442 } else {
3443 None
3444 };
3445 vec![edit_prediction, context_menu]
3446 .into_iter()
3447 .flatten()
3448 .collect::<Vec<_>>()
3449 },
3450 ) else {
3451 return;
3452 };
3453
3454 let Some((menu_ix, (_, menu_bounds))) = laid_out_popovers
3455 .iter()
3456 .find_position(|(x, _)| matches!(x, CursorPopoverType::CodeContextMenu))
3457 else {
3458 return;
3459 };
3460 let last_ix = laid_out_popovers.len() - 1;
3461 let menu_is_last = menu_ix == last_ix;
3462 let first_popover_bounds = laid_out_popovers[0].1;
3463 let last_popover_bounds = laid_out_popovers[last_ix].1;
3464
3465 // Bounds to layout the aside around. When y_flipped, the aside goes either above or to the
3466 // right, and otherwise it goes below or to the right.
3467 let mut target_bounds = Bounds::from_corners(
3468 first_popover_bounds.origin,
3469 last_popover_bounds.bottom_right(),
3470 );
3471 target_bounds.size.width = menu_bounds.size.width;
3472
3473 // Like `target_bounds`, but with the max height it could occupy. Choosing an aside position
3474 // based on this is preferred for layout stability.
3475 let mut max_target_bounds = target_bounds;
3476 max_target_bounds.size.height = max_height;
3477 if y_flipped {
3478 max_target_bounds.origin.y -= max_height - target_bounds.size.height;
3479 }
3480
3481 // Add spacing around `target_bounds` and `max_target_bounds`.
3482 let mut extend_amount = Edges::all(MENU_GAP);
3483 if y_flipped {
3484 extend_amount.bottom = line_height;
3485 } else {
3486 extend_amount.top = line_height;
3487 }
3488 let target_bounds = target_bounds.extend(extend_amount);
3489 let max_target_bounds = max_target_bounds.extend(extend_amount);
3490
3491 let must_place_above_or_below =
3492 if y_flipped && !menu_is_last && menu_bounds.size.height < max_menu_height {
3493 laid_out_popovers[menu_ix + 1..]
3494 .iter()
3495 .any(|(_, popover_bounds)| popover_bounds.size.width > menu_bounds.size.width)
3496 } else {
3497 false
3498 };
3499
3500 self.layout_context_menu_aside(
3501 y_flipped,
3502 *menu_bounds,
3503 target_bounds,
3504 max_target_bounds,
3505 max_menu_height,
3506 must_place_above_or_below,
3507 text_hitbox,
3508 viewport_bounds,
3509 window,
3510 cx,
3511 );
3512 }
3513
3514 fn layout_gutter_menu(
3515 &self,
3516 line_height: Pixels,
3517 text_hitbox: &Hitbox,
3518 content_origin: gpui::Point<Pixels>,
3519 scroll_pixel_position: gpui::Point<Pixels>,
3520 gutter_overshoot: Pixels,
3521 window: &mut Window,
3522 cx: &mut App,
3523 ) {
3524 let editor = self.editor.read(cx);
3525 if !editor.context_menu_visible() {
3526 return;
3527 }
3528 let Some(crate::ContextMenuOrigin::GutterIndicator(gutter_row)) =
3529 editor.context_menu_origin()
3530 else {
3531 return;
3532 };
3533 // Context menu was spawned via a click on a gutter. Ensure it's a bit closer to the
3534 // indicator than just a plain first column of the text field.
3535 let target_position = content_origin
3536 + gpui::Point {
3537 x: -gutter_overshoot,
3538 y: gutter_row.next_row().as_f32() * line_height - scroll_pixel_position.y,
3539 };
3540
3541 let (min_height_in_lines, max_height_in_lines) = editor
3542 .context_menu_options
3543 .as_ref()
3544 .map_or((3, 12), |options| {
3545 (options.min_entries_visible, options.max_entries_visible)
3546 });
3547
3548 let min_height = line_height * min_height_in_lines as f32 + POPOVER_Y_PADDING;
3549 let max_height = line_height * max_height_in_lines as f32 + POPOVER_Y_PADDING;
3550 let viewport_bounds =
3551 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
3552 right: -Self::SCROLLBAR_WIDTH - MENU_GAP,
3553 ..Default::default()
3554 });
3555 self.layout_popovers_above_or_below_line(
3556 target_position,
3557 line_height,
3558 min_height,
3559 max_height,
3560 editor
3561 .context_menu_options
3562 .as_ref()
3563 .and_then(|options| options.placement.clone()),
3564 text_hitbox,
3565 viewport_bounds,
3566 window,
3567 cx,
3568 move |height, _max_width_for_stable_x, _, window, cx| {
3569 let mut element = self
3570 .render_context_menu(line_height, height, window, cx)
3571 .expect("Visible context menu should always render.");
3572 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
3573 vec![(CursorPopoverType::CodeContextMenu, element, size)]
3574 },
3575 );
3576 }
3577
3578 fn layout_popovers_above_or_below_line(
3579 &self,
3580 target_position: gpui::Point<Pixels>,
3581 line_height: Pixels,
3582 min_height: Pixels,
3583 max_height: Pixels,
3584 placement: Option<ContextMenuPlacement>,
3585 text_hitbox: &Hitbox,
3586 viewport_bounds: Bounds<Pixels>,
3587 window: &mut Window,
3588 cx: &mut App,
3589 make_sized_popovers: impl FnOnce(
3590 Pixels,
3591 Pixels,
3592 bool,
3593 &mut Window,
3594 &mut App,
3595 ) -> Vec<(CursorPopoverType, AnyElement, Size<Pixels>)>,
3596 ) -> Option<(Vec<(CursorPopoverType, Bounds<Pixels>)>, bool)> {
3597 let text_style = TextStyleRefinement {
3598 line_height: Some(DefiniteLength::Fraction(
3599 BufferLineHeight::Comfortable.value(),
3600 )),
3601 ..Default::default()
3602 };
3603 window.with_text_style(Some(text_style), |window| {
3604 // If the max height won't fit below and there is more space above, put it above the line.
3605 let bottom_y_when_flipped = target_position.y - line_height;
3606 let available_above = bottom_y_when_flipped - text_hitbox.top();
3607 let available_below = text_hitbox.bottom() - target_position.y;
3608 let y_overflows_below = max_height > available_below;
3609 let mut y_flipped = match placement {
3610 Some(ContextMenuPlacement::Above) => true,
3611 Some(ContextMenuPlacement::Below) => false,
3612 None => y_overflows_below && available_above > available_below,
3613 };
3614 let mut height = cmp::min(
3615 max_height,
3616 if y_flipped {
3617 available_above
3618 } else {
3619 available_below
3620 },
3621 );
3622
3623 // If the min height doesn't fit within text bounds, instead fit within the window.
3624 if height < min_height {
3625 let available_above = bottom_y_when_flipped;
3626 let available_below = viewport_bounds.bottom() - target_position.y;
3627 let (y_flipped_override, height_override) = match placement {
3628 Some(ContextMenuPlacement::Above) => {
3629 (true, cmp::min(available_above, min_height))
3630 }
3631 Some(ContextMenuPlacement::Below) => {
3632 (false, cmp::min(available_below, min_height))
3633 }
3634 None => {
3635 if available_below > min_height {
3636 (false, min_height)
3637 } else if available_above > min_height {
3638 (true, min_height)
3639 } else if available_above > available_below {
3640 (true, available_above)
3641 } else {
3642 (false, available_below)
3643 }
3644 }
3645 };
3646 y_flipped = y_flipped_override;
3647 height = height_override;
3648 }
3649
3650 let max_width_for_stable_x = viewport_bounds.right() - target_position.x;
3651
3652 // TODO: Use viewport_bounds.width as a max width so that it doesn't get clipped on the left
3653 // for very narrow windows.
3654 let popovers =
3655 make_sized_popovers(height, max_width_for_stable_x, y_flipped, window, cx);
3656 if popovers.is_empty() {
3657 return None;
3658 }
3659
3660 let max_width = popovers
3661 .iter()
3662 .map(|(_, _, size)| size.width)
3663 .max()
3664 .unwrap_or_default();
3665
3666 let mut current_position = gpui::Point {
3667 // Snap the right edge of the list to the right edge of the window if its horizontal bounds
3668 // overflow. Include space for the scrollbar.
3669 x: target_position
3670 .x
3671 .min((viewport_bounds.right() - max_width).max(Pixels::ZERO)),
3672 y: if y_flipped {
3673 bottom_y_when_flipped
3674 } else {
3675 target_position.y
3676 },
3677 };
3678
3679 let mut laid_out_popovers = popovers
3680 .into_iter()
3681 .map(|(popover_type, element, size)| {
3682 if y_flipped {
3683 current_position.y -= size.height;
3684 }
3685 let position = current_position;
3686 window.defer_draw(element, current_position, 1);
3687 if !y_flipped {
3688 current_position.y += size.height + MENU_GAP;
3689 } else {
3690 current_position.y -= MENU_GAP;
3691 }
3692 (popover_type, Bounds::new(position, size))
3693 })
3694 .collect::<Vec<_>>();
3695
3696 if y_flipped {
3697 laid_out_popovers.reverse();
3698 }
3699
3700 Some((laid_out_popovers, y_flipped))
3701 })
3702 }
3703
3704 fn layout_context_menu_aside(
3705 &self,
3706 y_flipped: bool,
3707 menu_bounds: Bounds<Pixels>,
3708 target_bounds: Bounds<Pixels>,
3709 max_target_bounds: Bounds<Pixels>,
3710 max_height: Pixels,
3711 must_place_above_or_below: bool,
3712 text_hitbox: &Hitbox,
3713 viewport_bounds: Bounds<Pixels>,
3714 window: &mut Window,
3715 cx: &mut App,
3716 ) {
3717 let available_within_viewport = target_bounds.space_within(&viewport_bounds);
3718 let positioned_aside = if available_within_viewport.right >= MENU_ASIDE_MIN_WIDTH
3719 && !must_place_above_or_below
3720 {
3721 let max_width = cmp::min(
3722 available_within_viewport.right - px(1.),
3723 MENU_ASIDE_MAX_WIDTH,
3724 );
3725 let Some(mut aside) = self.render_context_menu_aside(
3726 size(max_width, max_height - POPOVER_Y_PADDING),
3727 window,
3728 cx,
3729 ) else {
3730 return;
3731 };
3732 aside.layout_as_root(AvailableSpace::min_size(), window, cx);
3733 let right_position = point(target_bounds.right(), menu_bounds.origin.y);
3734 Some((aside, right_position))
3735 } else {
3736 let max_size = size(
3737 // TODO(mgsloan): Once the menu is bounded by viewport width the bound on viewport
3738 // won't be needed here.
3739 cmp::min(
3740 cmp::max(menu_bounds.size.width - px(2.), MENU_ASIDE_MIN_WIDTH),
3741 viewport_bounds.right(),
3742 ),
3743 cmp::min(
3744 max_height,
3745 cmp::max(
3746 available_within_viewport.top,
3747 available_within_viewport.bottom,
3748 ),
3749 ) - POPOVER_Y_PADDING,
3750 );
3751 let Some(mut aside) = self.render_context_menu_aside(max_size, window, cx) else {
3752 return;
3753 };
3754 let actual_size = aside.layout_as_root(AvailableSpace::min_size(), window, cx);
3755
3756 let top_position = point(
3757 menu_bounds.origin.x,
3758 target_bounds.top() - actual_size.height,
3759 );
3760 let bottom_position = point(menu_bounds.origin.x, target_bounds.bottom());
3761
3762 let fit_within = |available: Edges<Pixels>, wanted: Size<Pixels>| {
3763 // Prefer to fit on the same side of the line as the menu, then on the other side of
3764 // the line.
3765 if !y_flipped && wanted.height < available.bottom {
3766 Some(bottom_position)
3767 } else if !y_flipped && wanted.height < available.top {
3768 Some(top_position)
3769 } else if y_flipped && wanted.height < available.top {
3770 Some(top_position)
3771 } else if y_flipped && wanted.height < available.bottom {
3772 Some(bottom_position)
3773 } else {
3774 None
3775 }
3776 };
3777
3778 // Prefer choosing a direction using max sizes rather than actual size for stability.
3779 let available_within_text = max_target_bounds.space_within(&text_hitbox.bounds);
3780 let wanted = size(MENU_ASIDE_MAX_WIDTH, max_height);
3781 let aside_position = fit_within(available_within_text, wanted)
3782 // Fallback: fit max size in window.
3783 .or_else(|| fit_within(max_target_bounds.space_within(&viewport_bounds), wanted))
3784 // Fallback: fit actual size in window.
3785 .or_else(|| fit_within(available_within_viewport, actual_size));
3786
3787 aside_position.map(|position| (aside, position))
3788 };
3789
3790 // Skip drawing if it doesn't fit anywhere.
3791 if let Some((aside, position)) = positioned_aside {
3792 window.defer_draw(aside, position, 2);
3793 }
3794 }
3795
3796 fn render_context_menu(
3797 &self,
3798 line_height: Pixels,
3799 height: Pixels,
3800 window: &mut Window,
3801 cx: &mut App,
3802 ) -> Option<AnyElement> {
3803 let max_height_in_lines = ((height - POPOVER_Y_PADDING) / line_height).floor() as u32;
3804 self.editor.update(cx, |editor, cx| {
3805 editor.render_context_menu(&self.style, max_height_in_lines, window, cx)
3806 })
3807 }
3808
3809 fn render_context_menu_aside(
3810 &self,
3811 max_size: Size<Pixels>,
3812 window: &mut Window,
3813 cx: &mut App,
3814 ) -> Option<AnyElement> {
3815 if max_size.width < px(100.) || max_size.height < px(12.) {
3816 None
3817 } else {
3818 self.editor.update(cx, |editor, cx| {
3819 editor.render_context_menu_aside(max_size, window, cx)
3820 })
3821 }
3822 }
3823
3824 fn layout_mouse_context_menu(
3825 &self,
3826 editor_snapshot: &EditorSnapshot,
3827 visible_range: Range<DisplayRow>,
3828 content_origin: gpui::Point<Pixels>,
3829 window: &mut Window,
3830 cx: &mut App,
3831 ) -> Option<AnyElement> {
3832 let position = self.editor.update(cx, |editor, _cx| {
3833 let visible_start_point = editor.display_to_pixel_point(
3834 DisplayPoint::new(visible_range.start, 0),
3835 editor_snapshot,
3836 window,
3837 )?;
3838 let visible_end_point = editor.display_to_pixel_point(
3839 DisplayPoint::new(visible_range.end, 0),
3840 editor_snapshot,
3841 window,
3842 )?;
3843
3844 let mouse_context_menu = editor.mouse_context_menu.as_ref()?;
3845 let (source_display_point, position) = match mouse_context_menu.position {
3846 MenuPosition::PinnedToScreen(point) => (None, point),
3847 MenuPosition::PinnedToEditor { source, offset } => {
3848 let source_display_point = source.to_display_point(editor_snapshot);
3849 let source_point = editor.to_pixel_point(source, editor_snapshot, window)?;
3850 let position = content_origin + source_point + offset;
3851 (Some(source_display_point), position)
3852 }
3853 };
3854
3855 let source_included = source_display_point.map_or(true, |source_display_point| {
3856 visible_range
3857 .to_inclusive()
3858 .contains(&source_display_point.row())
3859 });
3860 let position_included =
3861 visible_start_point.y <= position.y && position.y <= visible_end_point.y;
3862 if !source_included && !position_included {
3863 None
3864 } else {
3865 Some(position)
3866 }
3867 })?;
3868
3869 let text_style = TextStyleRefinement {
3870 line_height: Some(DefiniteLength::Fraction(
3871 BufferLineHeight::Comfortable.value(),
3872 )),
3873 ..Default::default()
3874 };
3875 window.with_text_style(Some(text_style), |window| {
3876 let mut element = self.editor.update(cx, |editor, _| {
3877 let mouse_context_menu = editor.mouse_context_menu.as_ref()?;
3878 let context_menu = mouse_context_menu.context_menu.clone();
3879
3880 Some(
3881 deferred(
3882 anchored()
3883 .position(position)
3884 .child(context_menu)
3885 .anchor(Corner::TopLeft)
3886 .snap_to_window_with_margin(px(8.)),
3887 )
3888 .with_priority(1)
3889 .into_any(),
3890 )
3891 })?;
3892
3893 element.prepaint_as_root(position, AvailableSpace::min_size(), window, cx);
3894 Some(element)
3895 })
3896 }
3897
3898 fn layout_hover_popovers(
3899 &self,
3900 snapshot: &EditorSnapshot,
3901 hitbox: &Hitbox,
3902 text_hitbox: &Hitbox,
3903 visible_display_row_range: Range<DisplayRow>,
3904 content_origin: gpui::Point<Pixels>,
3905 scroll_pixel_position: gpui::Point<Pixels>,
3906 line_layouts: &[LineWithInvisibles],
3907 line_height: Pixels,
3908 em_width: Pixels,
3909 window: &mut Window,
3910 cx: &mut App,
3911 ) {
3912 struct MeasuredHoverPopover {
3913 element: AnyElement,
3914 size: Size<Pixels>,
3915 horizontal_offset: Pixels,
3916 }
3917
3918 let max_size = size(
3919 (120. * em_width) // Default size
3920 .min(hitbox.size.width / 2.) // Shrink to half of the editor width
3921 .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
3922 (16. * line_height) // Default size
3923 .min(hitbox.size.height / 2.) // Shrink to half of the editor height
3924 .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
3925 );
3926
3927 let hover_popovers = self.editor.update(cx, |editor, cx| {
3928 editor.hover_state.render(
3929 snapshot,
3930 visible_display_row_range.clone(),
3931 max_size,
3932 window,
3933 cx,
3934 )
3935 });
3936 let Some((position, hover_popovers)) = hover_popovers else {
3937 return;
3938 };
3939
3940 // This is safe because we check on layout whether the required row is available
3941 let hovered_row_layout =
3942 &line_layouts[position.row().minus(visible_display_row_range.start) as usize];
3943
3944 // Compute Hovered Point
3945 let x =
3946 hovered_row_layout.x_for_index(position.column() as usize) - scroll_pixel_position.x;
3947 let y = position.row().as_f32() * line_height - scroll_pixel_position.y;
3948 let hovered_point = content_origin + point(x, y);
3949
3950 let mut overall_height = Pixels::ZERO;
3951 let mut measured_hover_popovers = Vec::new();
3952 for mut hover_popover in hover_popovers {
3953 let size = hover_popover.layout_as_root(AvailableSpace::min_size(), window, cx);
3954 let horizontal_offset =
3955 (text_hitbox.top_right().x - (hovered_point.x + size.width)).min(Pixels::ZERO);
3956
3957 overall_height += HOVER_POPOVER_GAP + size.height;
3958
3959 measured_hover_popovers.push(MeasuredHoverPopover {
3960 element: hover_popover,
3961 size,
3962 horizontal_offset,
3963 });
3964 }
3965 overall_height += HOVER_POPOVER_GAP;
3966
3967 fn draw_occluder(
3968 width: Pixels,
3969 origin: gpui::Point<Pixels>,
3970 window: &mut Window,
3971 cx: &mut App,
3972 ) {
3973 let mut occlusion = div()
3974 .size_full()
3975 .occlude()
3976 .on_mouse_move(|_, _, cx| cx.stop_propagation())
3977 .into_any_element();
3978 occlusion.layout_as_root(size(width, HOVER_POPOVER_GAP).into(), window, cx);
3979 window.defer_draw(occlusion, origin, 2);
3980 }
3981
3982 if hovered_point.y > overall_height {
3983 // There is enough space above. Render popovers above the hovered point
3984 let mut current_y = hovered_point.y;
3985 for (position, popover) in measured_hover_popovers.into_iter().with_position() {
3986 let size = popover.size;
3987 let popover_origin = point(
3988 hovered_point.x + popover.horizontal_offset,
3989 current_y - size.height,
3990 );
3991
3992 window.defer_draw(popover.element, popover_origin, 2);
3993 if position != itertools::Position::Last {
3994 let origin = point(popover_origin.x, popover_origin.y - HOVER_POPOVER_GAP);
3995 draw_occluder(size.width, origin, window, cx);
3996 }
3997
3998 current_y = popover_origin.y - HOVER_POPOVER_GAP;
3999 }
4000 } else {
4001 // There is not enough space above. Render popovers below the hovered point
4002 let mut current_y = hovered_point.y + line_height;
4003 for (position, popover) in measured_hover_popovers.into_iter().with_position() {
4004 let size = popover.size;
4005 let popover_origin = point(hovered_point.x + popover.horizontal_offset, current_y);
4006
4007 window.defer_draw(popover.element, popover_origin, 2);
4008 if position != itertools::Position::Last {
4009 let origin = point(popover_origin.x, popover_origin.y + size.height);
4010 draw_occluder(size.width, origin, window, cx);
4011 }
4012
4013 current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
4014 }
4015 }
4016 }
4017
4018 fn layout_diff_hunk_controls(
4019 &self,
4020 row_range: Range<DisplayRow>,
4021 row_infos: &[RowInfo],
4022 text_hitbox: &Hitbox,
4023 position_map: &PositionMap,
4024 newest_cursor_position: Option<DisplayPoint>,
4025 line_height: Pixels,
4026 scroll_pixel_position: gpui::Point<Pixels>,
4027 display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
4028 editor: Entity<Editor>,
4029 window: &mut Window,
4030 cx: &mut App,
4031 ) -> Vec<AnyElement> {
4032 let render_diff_hunk_controls = editor.read(cx).render_diff_hunk_controls.clone();
4033 let point_for_position = position_map.point_for_position(window.mouse_position());
4034
4035 let mut controls = vec![];
4036
4037 let active_positions = [
4038 Some(point_for_position.previous_valid),
4039 newest_cursor_position,
4040 ];
4041
4042 for (hunk, _) in display_hunks {
4043 if let DisplayDiffHunk::Unfolded {
4044 display_row_range,
4045 multi_buffer_range,
4046 status,
4047 is_created_file,
4048 ..
4049 } = &hunk
4050 {
4051 if display_row_range.start < row_range.start
4052 || display_row_range.start >= row_range.end
4053 {
4054 continue;
4055 }
4056 let row_ix = (display_row_range.start - row_range.start).0 as usize;
4057 if row_infos[row_ix].diff_status.is_none() {
4058 continue;
4059 }
4060 if row_infos[row_ix]
4061 .diff_status
4062 .is_some_and(|status| status.is_added())
4063 && !status.is_added()
4064 {
4065 continue;
4066 }
4067 if active_positions
4068 .iter()
4069 .any(|p| p.map_or(false, |p| display_row_range.contains(&p.row())))
4070 {
4071 let y = display_row_range.start.as_f32() * line_height
4072 + text_hitbox.bounds.top()
4073 - scroll_pixel_position.y;
4074
4075 let mut element = render_diff_hunk_controls(
4076 display_row_range.start.0,
4077 status,
4078 multi_buffer_range.clone(),
4079 *is_created_file,
4080 line_height,
4081 &editor,
4082 window,
4083 cx,
4084 );
4085 let size =
4086 element.layout_as_root(size(px(100.0), line_height).into(), window, cx);
4087
4088 let x = text_hitbox.bounds.right()
4089 - self.style.scrollbar_width
4090 - px(10.)
4091 - size.width;
4092
4093 window.with_absolute_element_offset(gpui::Point::new(x, y), |window| {
4094 element.prepaint(window, cx)
4095 });
4096 controls.push(element);
4097 }
4098 }
4099 }
4100
4101 controls
4102 }
4103
4104 fn layout_signature_help(
4105 &self,
4106 hitbox: &Hitbox,
4107 content_origin: gpui::Point<Pixels>,
4108 scroll_pixel_position: gpui::Point<Pixels>,
4109 newest_selection_head: Option<DisplayPoint>,
4110 start_row: DisplayRow,
4111 line_layouts: &[LineWithInvisibles],
4112 line_height: Pixels,
4113 em_width: Pixels,
4114 window: &mut Window,
4115 cx: &mut App,
4116 ) {
4117 if !self.editor.focus_handle(cx).is_focused(window) {
4118 return;
4119 }
4120 let Some(newest_selection_head) = newest_selection_head else {
4121 return;
4122 };
4123 let selection_row = newest_selection_head.row();
4124 if selection_row < start_row {
4125 return;
4126 }
4127 let Some(cursor_row_layout) = line_layouts.get(selection_row.minus(start_row) as usize)
4128 else {
4129 return;
4130 };
4131
4132 let start_x = cursor_row_layout.x_for_index(newest_selection_head.column() as usize)
4133 - scroll_pixel_position.x
4134 + content_origin.x;
4135 let start_y =
4136 selection_row.as_f32() * line_height + content_origin.y - scroll_pixel_position.y;
4137
4138 let max_size = size(
4139 (120. * em_width) // Default size
4140 .min(hitbox.size.width / 2.) // Shrink to half of the editor width
4141 .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
4142 (16. * line_height) // Default size
4143 .min(hitbox.size.height / 2.) // Shrink to half of the editor height
4144 .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
4145 );
4146
4147 let maybe_element = self.editor.update(cx, |editor, cx| {
4148 if let Some(popover) = editor.signature_help_state.popover_mut() {
4149 let element = popover.render(max_size, cx);
4150 Some(element)
4151 } else {
4152 None
4153 }
4154 });
4155 if let Some(mut element) = maybe_element {
4156 let window_size = window.viewport_size();
4157 let size = element.layout_as_root(Size::<AvailableSpace>::default(), window, cx);
4158 let mut point = point(start_x, start_y - size.height);
4159
4160 // Adjusting to ensure the popover does not overflow in the X-axis direction.
4161 if point.x + size.width >= window_size.width {
4162 point.x = window_size.width - size.width;
4163 }
4164
4165 window.defer_draw(element, point, 1)
4166 }
4167 }
4168
4169 fn paint_background(&self, layout: &EditorLayout, window: &mut Window, cx: &mut App) {
4170 window.paint_layer(layout.hitbox.bounds, |window| {
4171 let scroll_top = layout.position_map.snapshot.scroll_position().y;
4172 let gutter_bg = cx.theme().colors().editor_gutter_background;
4173 window.paint_quad(fill(layout.gutter_hitbox.bounds, gutter_bg));
4174 window.paint_quad(fill(
4175 layout.position_map.text_hitbox.bounds,
4176 self.style.background,
4177 ));
4178
4179 if let EditorMode::Full { .. } = layout.mode {
4180 let mut active_rows = layout.active_rows.iter().peekable();
4181 while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
4182 let mut end_row = start_row.0;
4183 while active_rows
4184 .peek()
4185 .map_or(false, |(active_row, has_selection)| {
4186 active_row.0 == end_row + 1
4187 && has_selection.selection == contains_non_empty_selection.selection
4188 })
4189 {
4190 active_rows.next().unwrap();
4191 end_row += 1;
4192 }
4193
4194 if !contains_non_empty_selection.selection {
4195 let highlight_h_range =
4196 match layout.position_map.snapshot.current_line_highlight {
4197 CurrentLineHighlight::Gutter => Some(Range {
4198 start: layout.hitbox.left(),
4199 end: layout.gutter_hitbox.right(),
4200 }),
4201 CurrentLineHighlight::Line => Some(Range {
4202 start: layout.position_map.text_hitbox.bounds.left(),
4203 end: layout.position_map.text_hitbox.bounds.right(),
4204 }),
4205 CurrentLineHighlight::All => Some(Range {
4206 start: layout.hitbox.left(),
4207 end: layout.hitbox.right(),
4208 }),
4209 CurrentLineHighlight::None => None,
4210 };
4211 if let Some(range) = highlight_h_range {
4212 let active_line_bg = cx.theme().colors().editor_active_line_background;
4213 let bounds = Bounds {
4214 origin: point(
4215 range.start,
4216 layout.hitbox.origin.y
4217 + (start_row.as_f32() - scroll_top)
4218 * layout.position_map.line_height,
4219 ),
4220 size: size(
4221 range.end - range.start,
4222 layout.position_map.line_height
4223 * (end_row - start_row.0 + 1) as f32,
4224 ),
4225 };
4226 window.paint_quad(fill(bounds, active_line_bg));
4227 }
4228 }
4229 }
4230
4231 let mut paint_highlight = |highlight_row_start: DisplayRow,
4232 highlight_row_end: DisplayRow,
4233 highlight: crate::LineHighlight,
4234 edges| {
4235 let origin = point(
4236 layout.hitbox.origin.x,
4237 layout.hitbox.origin.y
4238 + (highlight_row_start.as_f32() - scroll_top)
4239 * layout.position_map.line_height,
4240 );
4241 let size = size(
4242 layout.hitbox.size.width,
4243 layout.position_map.line_height
4244 * highlight_row_end.next_row().minus(highlight_row_start) as f32,
4245 );
4246 let mut quad = fill(Bounds { origin, size }, highlight.background);
4247 if let Some(border_color) = highlight.border {
4248 quad.border_color = border_color;
4249 quad.border_widths = edges
4250 }
4251 window.paint_quad(quad);
4252 };
4253
4254 let mut current_paint: Option<(LineHighlight, Range<DisplayRow>, Edges<Pixels>)> =
4255 None;
4256 for (&new_row, &new_background) in &layout.highlighted_rows {
4257 match &mut current_paint {
4258 &mut Some((current_background, ref mut current_range, mut edges)) => {
4259 let new_range_started = current_background != new_background
4260 || current_range.end.next_row() != new_row;
4261 if new_range_started {
4262 if current_range.end.next_row() == new_row {
4263 edges.bottom = px(0.);
4264 };
4265 paint_highlight(
4266 current_range.start,
4267 current_range.end,
4268 current_background,
4269 edges,
4270 );
4271 let edges = Edges {
4272 top: if current_range.end.next_row() != new_row {
4273 px(1.)
4274 } else {
4275 px(0.)
4276 },
4277 bottom: px(1.),
4278 ..Default::default()
4279 };
4280 current_paint = Some((new_background, new_row..new_row, edges));
4281 continue;
4282 } else {
4283 current_range.end = current_range.end.next_row();
4284 }
4285 }
4286 None => {
4287 let edges = Edges {
4288 top: px(1.),
4289 bottom: px(1.),
4290 ..Default::default()
4291 };
4292 current_paint = Some((new_background, new_row..new_row, edges))
4293 }
4294 };
4295 }
4296 if let Some((color, range, edges)) = current_paint {
4297 paint_highlight(range.start, range.end, color, edges);
4298 }
4299
4300 let scroll_left =
4301 layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
4302
4303 for (wrap_position, active) in layout.wrap_guides.iter() {
4304 let x = (layout.position_map.text_hitbox.origin.x
4305 + *wrap_position
4306 + layout.position_map.em_width / 2.)
4307 - scroll_left;
4308
4309 let show_scrollbars = layout
4310 .scrollbars_layout
4311 .as_ref()
4312 .map_or(false, |layout| layout.visible);
4313
4314 if x < layout.position_map.text_hitbox.origin.x
4315 || (show_scrollbars && x > self.scrollbar_left(&layout.hitbox.bounds))
4316 {
4317 continue;
4318 }
4319
4320 let color = if *active {
4321 cx.theme().colors().editor_active_wrap_guide
4322 } else {
4323 cx.theme().colors().editor_wrap_guide
4324 };
4325 window.paint_quad(fill(
4326 Bounds {
4327 origin: point(x, layout.position_map.text_hitbox.origin.y),
4328 size: size(px(1.), layout.position_map.text_hitbox.size.height),
4329 },
4330 color,
4331 ));
4332 }
4333 }
4334 })
4335 }
4336
4337 fn paint_indent_guides(
4338 &mut self,
4339 layout: &mut EditorLayout,
4340 window: &mut Window,
4341 cx: &mut App,
4342 ) {
4343 let Some(indent_guides) = &layout.indent_guides else {
4344 return;
4345 };
4346
4347 let faded_color = |color: Hsla, alpha: f32| {
4348 let mut faded = color;
4349 faded.a = alpha;
4350 faded
4351 };
4352
4353 for indent_guide in indent_guides {
4354 let indent_accent_colors = cx.theme().accents().color_for_index(indent_guide.depth);
4355 let settings = indent_guide.settings;
4356
4357 // TODO fixed for now, expose them through themes later
4358 const INDENT_AWARE_ALPHA: f32 = 0.2;
4359 const INDENT_AWARE_ACTIVE_ALPHA: f32 = 0.4;
4360 const INDENT_AWARE_BACKGROUND_ALPHA: f32 = 0.1;
4361 const INDENT_AWARE_BACKGROUND_ACTIVE_ALPHA: f32 = 0.2;
4362
4363 let line_color = match (settings.coloring, indent_guide.active) {
4364 (IndentGuideColoring::Disabled, _) => None,
4365 (IndentGuideColoring::Fixed, false) => {
4366 Some(cx.theme().colors().editor_indent_guide)
4367 }
4368 (IndentGuideColoring::Fixed, true) => {
4369 Some(cx.theme().colors().editor_indent_guide_active)
4370 }
4371 (IndentGuideColoring::IndentAware, false) => {
4372 Some(faded_color(indent_accent_colors, INDENT_AWARE_ALPHA))
4373 }
4374 (IndentGuideColoring::IndentAware, true) => {
4375 Some(faded_color(indent_accent_colors, INDENT_AWARE_ACTIVE_ALPHA))
4376 }
4377 };
4378
4379 let background_color = match (settings.background_coloring, indent_guide.active) {
4380 (IndentGuideBackgroundColoring::Disabled, _) => None,
4381 (IndentGuideBackgroundColoring::IndentAware, false) => Some(faded_color(
4382 indent_accent_colors,
4383 INDENT_AWARE_BACKGROUND_ALPHA,
4384 )),
4385 (IndentGuideBackgroundColoring::IndentAware, true) => Some(faded_color(
4386 indent_accent_colors,
4387 INDENT_AWARE_BACKGROUND_ACTIVE_ALPHA,
4388 )),
4389 };
4390
4391 let requested_line_width = if indent_guide.active {
4392 settings.active_line_width
4393 } else {
4394 settings.line_width
4395 }
4396 .clamp(1, 10);
4397 let mut line_indicator_width = 0.;
4398 if let Some(color) = line_color {
4399 window.paint_quad(fill(
4400 Bounds {
4401 origin: indent_guide.origin,
4402 size: size(px(requested_line_width as f32), indent_guide.length),
4403 },
4404 color,
4405 ));
4406 line_indicator_width = requested_line_width as f32;
4407 }
4408
4409 if let Some(color) = background_color {
4410 let width = indent_guide.single_indent_width - px(line_indicator_width);
4411 window.paint_quad(fill(
4412 Bounds {
4413 origin: point(
4414 indent_guide.origin.x + px(line_indicator_width),
4415 indent_guide.origin.y,
4416 ),
4417 size: size(width, indent_guide.length),
4418 },
4419 color,
4420 ));
4421 }
4422 }
4423 }
4424
4425 fn paint_line_numbers(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
4426 let is_singleton = self.editor.read(cx).is_singleton(cx);
4427
4428 let line_height = layout.position_map.line_height;
4429 window.set_cursor_style(CursorStyle::Arrow, Some(&layout.gutter_hitbox));
4430
4431 for LineNumberLayout {
4432 shaped_line,
4433 hitbox,
4434 } in layout.line_numbers.values()
4435 {
4436 let Some(hitbox) = hitbox else {
4437 continue;
4438 };
4439
4440 let Some(()) = (if !is_singleton && hitbox.is_hovered(window) {
4441 let color = cx.theme().colors().editor_hover_line_number;
4442
4443 let Some(line) = self
4444 .shape_line_number(shaped_line.text.clone(), color, window)
4445 .log_err()
4446 else {
4447 continue;
4448 };
4449
4450 line.paint(hitbox.origin, line_height, window, cx).log_err()
4451 } else {
4452 shaped_line
4453 .paint(hitbox.origin, line_height, window, cx)
4454 .log_err()
4455 }) else {
4456 continue;
4457 };
4458
4459 // In singleton buffers, we select corresponding lines on the line number click, so use | -like cursor.
4460 // In multi buffers, we open file at the line number clicked, so use a pointing hand cursor.
4461 if is_singleton {
4462 window.set_cursor_style(CursorStyle::IBeam, Some(&hitbox));
4463 } else {
4464 window.set_cursor_style(CursorStyle::PointingHand, Some(&hitbox));
4465 }
4466 }
4467 }
4468
4469 fn paint_gutter_diff_hunks(layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
4470 if layout.display_hunks.is_empty() {
4471 return;
4472 }
4473
4474 let line_height = layout.position_map.line_height;
4475 window.paint_layer(layout.gutter_hitbox.bounds, |window| {
4476 for (hunk, hitbox) in &layout.display_hunks {
4477 let hunk_to_paint = match hunk {
4478 DisplayDiffHunk::Folded { .. } => {
4479 let hunk_bounds = Self::diff_hunk_bounds(
4480 &layout.position_map.snapshot,
4481 line_height,
4482 layout.gutter_hitbox.bounds,
4483 &hunk,
4484 );
4485 Some((
4486 hunk_bounds,
4487 cx.theme().colors().version_control_modified,
4488 Corners::all(px(0.)),
4489 DiffHunkStatus::modified_none(),
4490 ))
4491 }
4492 DisplayDiffHunk::Unfolded {
4493 status,
4494 display_row_range,
4495 ..
4496 } => hitbox.as_ref().map(|hunk_hitbox| match status.kind {
4497 DiffHunkStatusKind::Added => (
4498 hunk_hitbox.bounds,
4499 cx.theme().colors().version_control_added,
4500 Corners::all(px(0.)),
4501 *status,
4502 ),
4503 DiffHunkStatusKind::Modified => (
4504 hunk_hitbox.bounds,
4505 cx.theme().colors().version_control_modified,
4506 Corners::all(px(0.)),
4507 *status,
4508 ),
4509 DiffHunkStatusKind::Deleted if !display_row_range.is_empty() => (
4510 hunk_hitbox.bounds,
4511 cx.theme().colors().version_control_deleted,
4512 Corners::all(px(0.)),
4513 *status,
4514 ),
4515 DiffHunkStatusKind::Deleted => (
4516 Bounds::new(
4517 point(
4518 hunk_hitbox.origin.x - hunk_hitbox.size.width,
4519 hunk_hitbox.origin.y,
4520 ),
4521 size(hunk_hitbox.size.width * 2., hunk_hitbox.size.height),
4522 ),
4523 cx.theme().colors().version_control_deleted,
4524 Corners::all(1. * line_height),
4525 *status,
4526 ),
4527 }),
4528 };
4529
4530 if let Some((hunk_bounds, background_color, corner_radii, status)) = hunk_to_paint {
4531 // Flatten the background color with the editor color to prevent
4532 // elements below transparent hunks from showing through
4533 let flattened_background_color = cx
4534 .theme()
4535 .colors()
4536 .editor_background
4537 .blend(background_color);
4538
4539 if !Self::diff_hunk_hollow(status, cx) {
4540 window.paint_quad(quad(
4541 hunk_bounds,
4542 corner_radii,
4543 flattened_background_color,
4544 Edges::default(),
4545 transparent_black(),
4546 BorderStyle::default(),
4547 ));
4548 } else {
4549 let flattened_unstaged_background_color = cx
4550 .theme()
4551 .colors()
4552 .editor_background
4553 .blend(background_color.opacity(0.3));
4554
4555 window.paint_quad(quad(
4556 hunk_bounds,
4557 corner_radii,
4558 flattened_unstaged_background_color,
4559 Edges::all(Pixels(1.0)),
4560 flattened_background_color,
4561 BorderStyle::Solid,
4562 ));
4563 }
4564 }
4565 }
4566 });
4567 }
4568
4569 fn gutter_strip_width(line_height: Pixels) -> Pixels {
4570 (0.275 * line_height).floor()
4571 }
4572
4573 fn diff_hunk_bounds(
4574 snapshot: &EditorSnapshot,
4575 line_height: Pixels,
4576 gutter_bounds: Bounds<Pixels>,
4577 hunk: &DisplayDiffHunk,
4578 ) -> Bounds<Pixels> {
4579 let scroll_position = snapshot.scroll_position();
4580 let scroll_top = scroll_position.y * line_height;
4581 let gutter_strip_width = Self::gutter_strip_width(line_height);
4582
4583 match hunk {
4584 DisplayDiffHunk::Folded { display_row, .. } => {
4585 let start_y = display_row.as_f32() * line_height - scroll_top;
4586 let end_y = start_y + line_height;
4587 let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
4588 let highlight_size = size(gutter_strip_width, end_y - start_y);
4589 Bounds::new(highlight_origin, highlight_size)
4590 }
4591 DisplayDiffHunk::Unfolded {
4592 display_row_range,
4593 status,
4594 ..
4595 } => {
4596 if status.is_deleted() && display_row_range.is_empty() {
4597 let row = display_row_range.start;
4598
4599 let offset = line_height / 2.;
4600 let start_y = row.as_f32() * line_height - offset - scroll_top;
4601 let end_y = start_y + line_height;
4602
4603 let width = (0.35 * line_height).floor();
4604 let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
4605 let highlight_size = size(width, end_y - start_y);
4606 Bounds::new(highlight_origin, highlight_size)
4607 } else {
4608 let start_row = display_row_range.start;
4609 let end_row = display_row_range.end;
4610 // If we're in a multibuffer, row range span might include an
4611 // excerpt header, so if we were to draw the marker straight away,
4612 // the hunk might include the rows of that header.
4613 // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap.
4614 // Instead, we simply check whether the range we're dealing with includes
4615 // any excerpt headers and if so, we stop painting the diff hunk on the first row of that header.
4616 let end_row_in_current_excerpt = snapshot
4617 .blocks_in_range(start_row..end_row)
4618 .find_map(|(start_row, block)| {
4619 if matches!(block, Block::ExcerptBoundary { .. }) {
4620 Some(start_row)
4621 } else {
4622 None
4623 }
4624 })
4625 .unwrap_or(end_row);
4626
4627 let start_y = start_row.as_f32() * line_height - scroll_top;
4628 let end_y = end_row_in_current_excerpt.as_f32() * line_height - scroll_top;
4629
4630 let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
4631 let highlight_size = size(gutter_strip_width, end_y - start_y);
4632 Bounds::new(highlight_origin, highlight_size)
4633 }
4634 }
4635 }
4636 }
4637
4638 fn paint_gutter_indicators(
4639 &self,
4640 layout: &mut EditorLayout,
4641 window: &mut Window,
4642 cx: &mut App,
4643 ) {
4644 window.paint_layer(layout.gutter_hitbox.bounds, |window| {
4645 window.with_element_namespace("crease_toggles", |window| {
4646 for crease_toggle in layout.crease_toggles.iter_mut().flatten() {
4647 crease_toggle.paint(window, cx);
4648 }
4649 });
4650
4651 window.with_element_namespace("expand_toggles", |window| {
4652 for (expand_toggle, _) in layout.expand_toggles.iter_mut().flatten() {
4653 expand_toggle.paint(window, cx);
4654 }
4655 });
4656
4657 for breakpoint in layout.breakpoints.iter_mut() {
4658 breakpoint.paint(window, cx);
4659 }
4660
4661 for test_indicator in layout.test_indicators.iter_mut() {
4662 test_indicator.paint(window, cx);
4663 }
4664
4665 if let Some(indicator) = layout.code_actions_indicator.as_mut() {
4666 indicator.paint(window, cx);
4667 }
4668 });
4669 }
4670
4671 fn paint_gutter_highlights(
4672 &self,
4673 layout: &mut EditorLayout,
4674 window: &mut Window,
4675 cx: &mut App,
4676 ) {
4677 for (_, hunk_hitbox) in &layout.display_hunks {
4678 if let Some(hunk_hitbox) = hunk_hitbox {
4679 if !self
4680 .editor
4681 .read(cx)
4682 .buffer()
4683 .read(cx)
4684 .all_diff_hunks_expanded()
4685 {
4686 window.set_cursor_style(CursorStyle::PointingHand, Some(hunk_hitbox));
4687 }
4688 }
4689 }
4690
4691 let show_git_gutter = layout
4692 .position_map
4693 .snapshot
4694 .show_git_diff_gutter
4695 .unwrap_or_else(|| {
4696 matches!(
4697 ProjectSettings::get_global(cx).git.git_gutter,
4698 Some(GitGutterSetting::TrackedFiles)
4699 )
4700 });
4701 if show_git_gutter {
4702 Self::paint_gutter_diff_hunks(layout, window, cx)
4703 }
4704
4705 let highlight_width = 0.275 * layout.position_map.line_height;
4706 let highlight_corner_radii = Corners::all(0.05 * layout.position_map.line_height);
4707 window.paint_layer(layout.gutter_hitbox.bounds, |window| {
4708 for (range, color) in &layout.highlighted_gutter_ranges {
4709 let start_row = if range.start.row() < layout.visible_display_row_range.start {
4710 layout.visible_display_row_range.start - DisplayRow(1)
4711 } else {
4712 range.start.row()
4713 };
4714 let end_row = if range.end.row() > layout.visible_display_row_range.end {
4715 layout.visible_display_row_range.end + DisplayRow(1)
4716 } else {
4717 range.end.row()
4718 };
4719
4720 let start_y = layout.gutter_hitbox.top()
4721 + start_row.0 as f32 * layout.position_map.line_height
4722 - layout.position_map.scroll_pixel_position.y;
4723 let end_y = layout.gutter_hitbox.top()
4724 + (end_row.0 + 1) as f32 * layout.position_map.line_height
4725 - layout.position_map.scroll_pixel_position.y;
4726 let bounds = Bounds::from_corners(
4727 point(layout.gutter_hitbox.left(), start_y),
4728 point(layout.gutter_hitbox.left() + highlight_width, end_y),
4729 );
4730 window.paint_quad(fill(bounds, *color).corner_radii(highlight_corner_radii));
4731 }
4732 });
4733 }
4734
4735 fn paint_blamed_display_rows(
4736 &self,
4737 layout: &mut EditorLayout,
4738 window: &mut Window,
4739 cx: &mut App,
4740 ) {
4741 let Some(blamed_display_rows) = layout.blamed_display_rows.take() else {
4742 return;
4743 };
4744
4745 window.paint_layer(layout.gutter_hitbox.bounds, |window| {
4746 for mut blame_element in blamed_display_rows.into_iter() {
4747 blame_element.paint(window, cx);
4748 }
4749 })
4750 }
4751
4752 fn paint_text(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
4753 window.with_content_mask(
4754 Some(ContentMask {
4755 bounds: layout.position_map.text_hitbox.bounds,
4756 }),
4757 |window| {
4758 let editor = self.editor.read(cx);
4759 if editor.mouse_cursor_hidden {
4760 window.set_cursor_style(CursorStyle::None, None);
4761 } else if editor
4762 .hovered_link_state
4763 .as_ref()
4764 .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
4765 {
4766 window.set_cursor_style(
4767 CursorStyle::PointingHand,
4768 Some(&layout.position_map.text_hitbox),
4769 );
4770 } else {
4771 window.set_cursor_style(
4772 CursorStyle::IBeam,
4773 Some(&layout.position_map.text_hitbox),
4774 );
4775 };
4776
4777 self.paint_lines_background(layout, window, cx);
4778 let invisible_display_ranges = self.paint_highlights(layout, window);
4779 self.paint_lines(&invisible_display_ranges, layout, window, cx);
4780 self.paint_redactions(layout, window);
4781 self.paint_cursors(layout, window, cx);
4782 self.paint_inline_diagnostics(layout, window, cx);
4783 self.paint_inline_blame(layout, window, cx);
4784 self.paint_diff_hunk_controls(layout, window, cx);
4785 window.with_element_namespace("crease_trailers", |window| {
4786 for trailer in layout.crease_trailers.iter_mut().flatten() {
4787 trailer.element.paint(window, cx);
4788 }
4789 });
4790 },
4791 )
4792 }
4793
4794 fn paint_highlights(
4795 &mut self,
4796 layout: &mut EditorLayout,
4797 window: &mut Window,
4798 ) -> SmallVec<[Range<DisplayPoint>; 32]> {
4799 window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
4800 let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
4801 let line_end_overshoot = 0.15 * layout.position_map.line_height;
4802 for (range, color) in &layout.highlighted_ranges {
4803 self.paint_highlighted_range(
4804 range.clone(),
4805 *color,
4806 Pixels::ZERO,
4807 line_end_overshoot,
4808 layout,
4809 window,
4810 );
4811 }
4812
4813 let corner_radius = 0.15 * layout.position_map.line_height;
4814
4815 for (player_color, selections) in &layout.selections {
4816 for selection in selections.iter() {
4817 self.paint_highlighted_range(
4818 selection.range.clone(),
4819 player_color.selection,
4820 corner_radius,
4821 corner_radius * 2.,
4822 layout,
4823 window,
4824 );
4825
4826 if selection.is_local && !selection.range.is_empty() {
4827 invisible_display_ranges.push(selection.range.clone());
4828 }
4829 }
4830 }
4831 invisible_display_ranges
4832 })
4833 }
4834
4835 fn paint_lines(
4836 &mut self,
4837 invisible_display_ranges: &[Range<DisplayPoint>],
4838 layout: &mut EditorLayout,
4839 window: &mut Window,
4840 cx: &mut App,
4841 ) {
4842 let whitespace_setting = self
4843 .editor
4844 .read(cx)
4845 .buffer
4846 .read(cx)
4847 .language_settings(cx)
4848 .show_whitespaces;
4849
4850 for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
4851 let row = DisplayRow(layout.visible_display_row_range.start.0 + ix as u32);
4852 line_with_invisibles.draw(
4853 layout,
4854 row,
4855 layout.content_origin,
4856 whitespace_setting,
4857 invisible_display_ranges,
4858 window,
4859 cx,
4860 )
4861 }
4862
4863 for line_element in &mut layout.line_elements {
4864 line_element.paint(window, cx);
4865 }
4866 }
4867
4868 fn paint_lines_background(
4869 &mut self,
4870 layout: &mut EditorLayout,
4871 window: &mut Window,
4872 cx: &mut App,
4873 ) {
4874 for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
4875 let row = DisplayRow(layout.visible_display_row_range.start.0 + ix as u32);
4876 line_with_invisibles.draw_background(layout, row, layout.content_origin, window, cx);
4877 }
4878 }
4879
4880 fn paint_redactions(&mut self, layout: &EditorLayout, window: &mut Window) {
4881 if layout.redacted_ranges.is_empty() {
4882 return;
4883 }
4884
4885 let line_end_overshoot = layout.line_end_overshoot();
4886
4887 // A softer than perfect black
4888 let redaction_color = gpui::rgb(0x0e1111);
4889
4890 window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
4891 for range in layout.redacted_ranges.iter() {
4892 self.paint_highlighted_range(
4893 range.clone(),
4894 redaction_color.into(),
4895 Pixels::ZERO,
4896 line_end_overshoot,
4897 layout,
4898 window,
4899 );
4900 }
4901 });
4902 }
4903
4904 fn paint_cursors(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
4905 for cursor in &mut layout.visible_cursors {
4906 cursor.paint(layout.content_origin, window, cx);
4907 }
4908 }
4909
4910 fn paint_scrollbars(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
4911 let Some(scrollbars_layout) = &layout.scrollbars_layout else {
4912 return;
4913 };
4914
4915 for (scrollbar_layout, axis) in scrollbars_layout.iter_scrollbars() {
4916 let hitbox = &scrollbar_layout.hitbox;
4917 let thumb_bounds = scrollbar_layout.thumb_bounds();
4918
4919 if scrollbars_layout.visible {
4920 let scrollbar_edges = match axis {
4921 ScrollbarAxis::Horizontal => Edges {
4922 top: Pixels::ZERO,
4923 right: Pixels::ZERO,
4924 bottom: Pixels::ZERO,
4925 left: Pixels::ZERO,
4926 },
4927 ScrollbarAxis::Vertical => Edges {
4928 top: Pixels::ZERO,
4929 right: Pixels::ZERO,
4930 bottom: Pixels::ZERO,
4931 left: ScrollbarLayout::BORDER_WIDTH,
4932 },
4933 };
4934
4935 window.paint_layer(hitbox.bounds, |window| {
4936 window.paint_quad(quad(
4937 hitbox.bounds,
4938 Corners::default(),
4939 cx.theme().colors().scrollbar_track_background,
4940 scrollbar_edges,
4941 cx.theme().colors().scrollbar_track_border,
4942 BorderStyle::Solid,
4943 ));
4944
4945 if axis == ScrollbarAxis::Vertical {
4946 let fast_markers =
4947 self.collect_fast_scrollbar_markers(layout, &scrollbar_layout, cx);
4948 // Refresh slow scrollbar markers in the background. Below, we
4949 // paint whatever markers have already been computed.
4950 self.refresh_slow_scrollbar_markers(layout, &scrollbar_layout, window, cx);
4951
4952 let markers = self.editor.read(cx).scrollbar_marker_state.markers.clone();
4953 for marker in markers.iter().chain(&fast_markers) {
4954 let mut marker = marker.clone();
4955 marker.bounds.origin += hitbox.origin;
4956 window.paint_quad(marker);
4957 }
4958 }
4959
4960 window.paint_quad(quad(
4961 thumb_bounds,
4962 Corners::default(),
4963 cx.theme().colors().scrollbar_thumb_background,
4964 scrollbar_edges,
4965 cx.theme().colors().scrollbar_thumb_border,
4966 BorderStyle::Solid,
4967 ));
4968 })
4969 }
4970 window.set_cursor_style(CursorStyle::Arrow, Some(&hitbox));
4971 }
4972
4973 window.on_mouse_event({
4974 let editor = self.editor.clone();
4975 let scrollbars_layout = scrollbars_layout.clone();
4976
4977 let mut mouse_position = window.mouse_position();
4978 move |event: &MouseMoveEvent, phase, window, cx| {
4979 if phase == DispatchPhase::Capture {
4980 return;
4981 }
4982
4983 editor.update(cx, |editor, cx| {
4984 if let Some((scrollbar_layout, axis)) = event
4985 .pressed_button
4986 .filter(|button| *button == MouseButton::Left)
4987 .and(editor.scroll_manager.dragging_scrollbar_axis())
4988 .and_then(|axis| {
4989 scrollbars_layout
4990 .iter_scrollbars()
4991 .find(|(_, a)| *a == axis)
4992 })
4993 {
4994 let ScrollbarLayout {
4995 hitbox,
4996 text_unit_size,
4997 ..
4998 } = scrollbar_layout;
4999
5000 let old_position = mouse_position.along(axis);
5001 let new_position = event.position.along(axis);
5002 if (hitbox.origin.along(axis)..hitbox.bottom_right().along(axis))
5003 .contains(&old_position)
5004 {
5005 let position = editor.scroll_position(cx).apply_along(axis, |p| {
5006 (p + (new_position - old_position) / *text_unit_size).max(0.)
5007 });
5008 editor.set_scroll_position(position, window, cx);
5009 }
5010 cx.stop_propagation();
5011 } else {
5012 editor.scroll_manager.reset_scrollbar_dragging_state(cx);
5013 }
5014
5015 if scrollbars_layout.get_hovered_axis(window).is_some() {
5016 editor.scroll_manager.show_scrollbars(window, cx);
5017 }
5018
5019 mouse_position = event.position;
5020 })
5021 }
5022 });
5023
5024 if self.editor.read(cx).scroll_manager.any_scrollbar_dragged() {
5025 window.on_mouse_event({
5026 let editor = self.editor.clone();
5027 move |_: &MouseUpEvent, phase, _, cx| {
5028 if phase == DispatchPhase::Capture {
5029 return;
5030 }
5031
5032 editor.update(cx, |editor, cx| {
5033 editor.scroll_manager.reset_scrollbar_dragging_state(cx);
5034 cx.stop_propagation();
5035 });
5036 }
5037 });
5038 } else {
5039 window.on_mouse_event({
5040 let editor = self.editor.clone();
5041 let scrollbars_layout = scrollbars_layout.clone();
5042
5043 move |event: &MouseDownEvent, phase, window, cx| {
5044 if phase == DispatchPhase::Capture {
5045 return;
5046 }
5047 let Some((scrollbar_layout, axis)) = scrollbars_layout.get_hovered_axis(window)
5048 else {
5049 return;
5050 };
5051
5052 let ScrollbarLayout {
5053 hitbox,
5054 visible_range,
5055 text_unit_size,
5056 ..
5057 } = scrollbar_layout;
5058
5059 let thumb_bounds = scrollbar_layout.thumb_bounds();
5060
5061 editor.update(cx, |editor, cx| {
5062 editor.scroll_manager.set_dragged_scrollbar_axis(axis, cx);
5063
5064 let event_position = event.position.along(axis);
5065
5066 if event_position < thumb_bounds.origin.along(axis)
5067 || thumb_bounds.bottom_right().along(axis) < event_position
5068 {
5069 let center_position = ((event_position - hitbox.origin.along(axis))
5070 / *text_unit_size)
5071 .round() as u32;
5072 let start_position = center_position.saturating_sub(
5073 (visible_range.end - visible_range.start) as u32 / 2,
5074 );
5075
5076 let position = editor
5077 .scroll_position(cx)
5078 .apply_along(axis, |_| start_position as f32);
5079
5080 editor.set_scroll_position(position, window, cx);
5081 } else {
5082 editor.scroll_manager.show_scrollbars(window, cx);
5083 }
5084
5085 cx.stop_propagation();
5086 });
5087 }
5088 });
5089 }
5090 }
5091
5092 fn collect_fast_scrollbar_markers(
5093 &self,
5094 layout: &EditorLayout,
5095 scrollbar_layout: &ScrollbarLayout,
5096 cx: &mut App,
5097 ) -> Vec<PaintQuad> {
5098 const LIMIT: usize = 100;
5099 if !EditorSettings::get_global(cx).scrollbar.cursors || layout.cursors.len() > LIMIT {
5100 return vec![];
5101 }
5102 let cursor_ranges = layout
5103 .cursors
5104 .iter()
5105 .map(|(point, color)| ColoredRange {
5106 start: point.row(),
5107 end: point.row(),
5108 color: *color,
5109 })
5110 .collect_vec();
5111 scrollbar_layout.marker_quads_for_ranges(cursor_ranges, None)
5112 }
5113
5114 fn refresh_slow_scrollbar_markers(
5115 &self,
5116 layout: &EditorLayout,
5117 scrollbar_layout: &ScrollbarLayout,
5118 window: &mut Window,
5119 cx: &mut App,
5120 ) {
5121 self.editor.update(cx, |editor, cx| {
5122 if !editor.is_singleton(cx)
5123 || !editor
5124 .scrollbar_marker_state
5125 .should_refresh(scrollbar_layout.hitbox.size)
5126 {
5127 return;
5128 }
5129
5130 let scrollbar_layout = scrollbar_layout.clone();
5131 let background_highlights = editor.background_highlights.clone();
5132 let snapshot = layout.position_map.snapshot.clone();
5133 let theme = cx.theme().clone();
5134 let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
5135
5136 editor.scrollbar_marker_state.dirty = false;
5137 editor.scrollbar_marker_state.pending_refresh =
5138 Some(cx.spawn_in(window, async move |editor, cx| {
5139 let scrollbar_size = scrollbar_layout.hitbox.size;
5140 let scrollbar_markers = cx
5141 .background_spawn(async move {
5142 let max_point = snapshot.display_snapshot.buffer_snapshot.max_point();
5143 let mut marker_quads = Vec::new();
5144 if scrollbar_settings.git_diff {
5145 let marker_row_ranges =
5146 snapshot.buffer_snapshot.diff_hunks().map(|hunk| {
5147 let start_display_row =
5148 MultiBufferPoint::new(hunk.row_range.start.0, 0)
5149 .to_display_point(&snapshot.display_snapshot)
5150 .row();
5151 let mut end_display_row =
5152 MultiBufferPoint::new(hunk.row_range.end.0, 0)
5153 .to_display_point(&snapshot.display_snapshot)
5154 .row();
5155 if end_display_row != start_display_row {
5156 end_display_row.0 -= 1;
5157 }
5158 let color = match &hunk.status().kind {
5159 DiffHunkStatusKind::Added => {
5160 theme.colors().version_control_added
5161 }
5162 DiffHunkStatusKind::Modified => {
5163 theme.colors().version_control_modified
5164 }
5165 DiffHunkStatusKind::Deleted => {
5166 theme.colors().version_control_deleted
5167 }
5168 };
5169 ColoredRange {
5170 start: start_display_row,
5171 end: end_display_row,
5172 color,
5173 }
5174 });
5175
5176 marker_quads.extend(
5177 scrollbar_layout
5178 .marker_quads_for_ranges(marker_row_ranges, Some(0)),
5179 );
5180 }
5181
5182 for (background_highlight_id, (_, background_ranges)) in
5183 background_highlights.iter()
5184 {
5185 let is_search_highlights = *background_highlight_id
5186 == TypeId::of::<BufferSearchHighlights>();
5187 let is_text_highlights = *background_highlight_id
5188 == TypeId::of::<SelectedTextHighlight>();
5189 let is_symbol_occurrences = *background_highlight_id
5190 == TypeId::of::<DocumentHighlightRead>()
5191 || *background_highlight_id
5192 == TypeId::of::<DocumentHighlightWrite>();
5193 if (is_search_highlights && scrollbar_settings.search_results)
5194 || (is_text_highlights && scrollbar_settings.selected_text)
5195 || (is_symbol_occurrences && scrollbar_settings.selected_symbol)
5196 {
5197 let mut color = theme.status().info;
5198 if is_symbol_occurrences {
5199 color.fade_out(0.5);
5200 }
5201 let marker_row_ranges = background_ranges.iter().map(|range| {
5202 let display_start = range
5203 .start
5204 .to_display_point(&snapshot.display_snapshot);
5205 let display_end =
5206 range.end.to_display_point(&snapshot.display_snapshot);
5207 ColoredRange {
5208 start: display_start.row(),
5209 end: display_end.row(),
5210 color,
5211 }
5212 });
5213 marker_quads.extend(
5214 scrollbar_layout
5215 .marker_quads_for_ranges(marker_row_ranges, Some(1)),
5216 );
5217 }
5218 }
5219
5220 if scrollbar_settings.diagnostics != ScrollbarDiagnostics::None {
5221 let diagnostics = snapshot
5222 .buffer_snapshot
5223 .diagnostics_in_range::<Point>(Point::zero()..max_point)
5224 // Don't show diagnostics the user doesn't care about
5225 .filter(|diagnostic| {
5226 match (
5227 scrollbar_settings.diagnostics,
5228 diagnostic.diagnostic.severity,
5229 ) {
5230 (ScrollbarDiagnostics::All, _) => true,
5231 (
5232 ScrollbarDiagnostics::Error,
5233 DiagnosticSeverity::ERROR,
5234 ) => true,
5235 (
5236 ScrollbarDiagnostics::Warning,
5237 DiagnosticSeverity::ERROR
5238 | DiagnosticSeverity::WARNING,
5239 ) => true,
5240 (
5241 ScrollbarDiagnostics::Information,
5242 DiagnosticSeverity::ERROR
5243 | DiagnosticSeverity::WARNING
5244 | DiagnosticSeverity::INFORMATION,
5245 ) => true,
5246 (_, _) => false,
5247 }
5248 })
5249 // We want to sort by severity, in order to paint the most severe diagnostics last.
5250 .sorted_by_key(|diagnostic| {
5251 std::cmp::Reverse(diagnostic.diagnostic.severity)
5252 });
5253
5254 let marker_row_ranges = diagnostics.into_iter().map(|diagnostic| {
5255 let start_display = diagnostic
5256 .range
5257 .start
5258 .to_display_point(&snapshot.display_snapshot);
5259 let end_display = diagnostic
5260 .range
5261 .end
5262 .to_display_point(&snapshot.display_snapshot);
5263 let color = match diagnostic.diagnostic.severity {
5264 DiagnosticSeverity::ERROR => theme.status().error,
5265 DiagnosticSeverity::WARNING => theme.status().warning,
5266 DiagnosticSeverity::INFORMATION => theme.status().info,
5267 _ => theme.status().hint,
5268 };
5269 ColoredRange {
5270 start: start_display.row(),
5271 end: end_display.row(),
5272 color,
5273 }
5274 });
5275 marker_quads.extend(
5276 scrollbar_layout
5277 .marker_quads_for_ranges(marker_row_ranges, Some(2)),
5278 );
5279 }
5280
5281 Arc::from(marker_quads)
5282 })
5283 .await;
5284
5285 editor.update(cx, |editor, cx| {
5286 editor.scrollbar_marker_state.markers = scrollbar_markers;
5287 editor.scrollbar_marker_state.scrollbar_size = scrollbar_size;
5288 editor.scrollbar_marker_state.pending_refresh = None;
5289 cx.notify();
5290 })?;
5291
5292 Ok(())
5293 }));
5294 });
5295 }
5296
5297 fn paint_highlighted_range(
5298 &self,
5299 range: Range<DisplayPoint>,
5300 color: Hsla,
5301 corner_radius: Pixels,
5302 line_end_overshoot: Pixels,
5303 layout: &EditorLayout,
5304 window: &mut Window,
5305 ) {
5306 let start_row = layout.visible_display_row_range.start;
5307 let end_row = layout.visible_display_row_range.end;
5308 if range.start != range.end {
5309 let row_range = if range.end.column() == 0 {
5310 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
5311 } else {
5312 cmp::max(range.start.row(), start_row)
5313 ..cmp::min(range.end.row().next_row(), end_row)
5314 };
5315
5316 let highlighted_range = HighlightedRange {
5317 color,
5318 line_height: layout.position_map.line_height,
5319 corner_radius,
5320 start_y: layout.content_origin.y
5321 + row_range.start.as_f32() * layout.position_map.line_height
5322 - layout.position_map.scroll_pixel_position.y,
5323 lines: row_range
5324 .iter_rows()
5325 .map(|row| {
5326 let line_layout =
5327 &layout.position_map.line_layouts[row.minus(start_row) as usize];
5328 HighlightedRangeLine {
5329 start_x: if row == range.start.row() {
5330 layout.content_origin.x
5331 + line_layout.x_for_index(range.start.column() as usize)
5332 - layout.position_map.scroll_pixel_position.x
5333 } else {
5334 layout.content_origin.x
5335 - layout.position_map.scroll_pixel_position.x
5336 },
5337 end_x: if row == range.end.row() {
5338 layout.content_origin.x
5339 + line_layout.x_for_index(range.end.column() as usize)
5340 - layout.position_map.scroll_pixel_position.x
5341 } else {
5342 layout.content_origin.x + line_layout.width + line_end_overshoot
5343 - layout.position_map.scroll_pixel_position.x
5344 },
5345 }
5346 })
5347 .collect(),
5348 };
5349
5350 highlighted_range.paint(layout.position_map.text_hitbox.bounds, window);
5351 }
5352 }
5353
5354 fn paint_inline_diagnostics(
5355 &mut self,
5356 layout: &mut EditorLayout,
5357 window: &mut Window,
5358 cx: &mut App,
5359 ) {
5360 for mut inline_diagnostic in layout.inline_diagnostics.drain() {
5361 inline_diagnostic.1.paint(window, cx);
5362 }
5363 }
5364
5365 fn paint_inline_blame(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
5366 if let Some(mut inline_blame) = layout.inline_blame.take() {
5367 window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
5368 inline_blame.paint(window, cx);
5369 })
5370 }
5371 }
5372
5373 fn paint_diff_hunk_controls(
5374 &mut self,
5375 layout: &mut EditorLayout,
5376 window: &mut Window,
5377 cx: &mut App,
5378 ) {
5379 for mut diff_hunk_control in layout.diff_hunk_controls.drain(..) {
5380 diff_hunk_control.paint(window, cx);
5381 }
5382 }
5383
5384 fn paint_blocks(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
5385 for mut block in layout.blocks.drain(..) {
5386 if block.overlaps_gutter {
5387 block.element.paint(window, cx);
5388 } else {
5389 let mut bounds = layout.hitbox.bounds;
5390 bounds.origin.x += layout.gutter_hitbox.bounds.size.width;
5391 window.with_content_mask(Some(ContentMask { bounds }), |window| {
5392 block.element.paint(window, cx);
5393 })
5394 }
5395 }
5396 }
5397
5398 fn paint_inline_completion_popover(
5399 &mut self,
5400 layout: &mut EditorLayout,
5401 window: &mut Window,
5402 cx: &mut App,
5403 ) {
5404 if let Some(inline_completion_popover) = layout.inline_completion_popover.as_mut() {
5405 inline_completion_popover.paint(window, cx);
5406 }
5407 }
5408
5409 fn paint_mouse_context_menu(
5410 &mut self,
5411 layout: &mut EditorLayout,
5412 window: &mut Window,
5413 cx: &mut App,
5414 ) {
5415 if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() {
5416 mouse_context_menu.paint(window, cx);
5417 }
5418 }
5419
5420 fn paint_scroll_wheel_listener(
5421 &mut self,
5422 layout: &EditorLayout,
5423 window: &mut Window,
5424 cx: &mut App,
5425 ) {
5426 window.on_mouse_event({
5427 let position_map = layout.position_map.clone();
5428 let editor = self.editor.clone();
5429 let hitbox = layout.hitbox.clone();
5430 let mut delta = ScrollDelta::default();
5431
5432 // Set a minimum scroll_sensitivity of 0.01 to make sure the user doesn't
5433 // accidentally turn off their scrolling.
5434 let scroll_sensitivity = EditorSettings::get_global(cx).scroll_sensitivity.max(0.01);
5435
5436 move |event: &ScrollWheelEvent, phase, window, cx| {
5437 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
5438 delta = delta.coalesce(event.delta);
5439 editor.update(cx, |editor, cx| {
5440 let position_map: &PositionMap = &position_map;
5441
5442 let line_height = position_map.line_height;
5443 let max_glyph_width = position_map.em_width;
5444 let (delta, axis) = match delta {
5445 gpui::ScrollDelta::Pixels(mut pixels) => {
5446 //Trackpad
5447 let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
5448 (pixels, axis)
5449 }
5450
5451 gpui::ScrollDelta::Lines(lines) => {
5452 //Not trackpad
5453 let pixels =
5454 point(lines.x * max_glyph_width, lines.y * line_height);
5455 (pixels, None)
5456 }
5457 };
5458
5459 let current_scroll_position = position_map.snapshot.scroll_position();
5460 let x = (current_scroll_position.x * max_glyph_width
5461 - (delta.x * scroll_sensitivity))
5462 / max_glyph_width;
5463 let y = (current_scroll_position.y * line_height
5464 - (delta.y * scroll_sensitivity))
5465 / line_height;
5466 let mut scroll_position =
5467 point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
5468 let forbid_vertical_scroll = editor.scroll_manager.forbid_vertical_scroll();
5469 if forbid_vertical_scroll {
5470 scroll_position.y = current_scroll_position.y;
5471 }
5472
5473 if scroll_position != current_scroll_position {
5474 editor.scroll(scroll_position, axis, window, cx);
5475 cx.stop_propagation();
5476 } else if y < 0. {
5477 // Due to clamping, we may fail to detect cases of overscroll to the top;
5478 // We want the scroll manager to get an update in such cases and detect the change of direction
5479 // on the next frame.
5480 cx.notify();
5481 }
5482 });
5483 }
5484 }
5485 });
5486 }
5487
5488 fn paint_mouse_listeners(&mut self, layout: &EditorLayout, window: &mut Window, cx: &mut App) {
5489 self.paint_scroll_wheel_listener(layout, window, cx);
5490
5491 window.on_mouse_event({
5492 let position_map = layout.position_map.clone();
5493 let editor = self.editor.clone();
5494 let diff_hunk_range =
5495 layout
5496 .display_hunks
5497 .iter()
5498 .find_map(|(hunk, hunk_hitbox)| match hunk {
5499 DisplayDiffHunk::Folded { .. } => None,
5500 DisplayDiffHunk::Unfolded {
5501 multi_buffer_range, ..
5502 } => {
5503 if hunk_hitbox
5504 .as_ref()
5505 .map(|hitbox| hitbox.is_hovered(window))
5506 .unwrap_or(false)
5507 {
5508 Some(multi_buffer_range.clone())
5509 } else {
5510 None
5511 }
5512 }
5513 });
5514 let line_numbers = layout.line_numbers.clone();
5515
5516 move |event: &MouseDownEvent, phase, window, cx| {
5517 if phase == DispatchPhase::Bubble {
5518 match event.button {
5519 MouseButton::Left => editor.update(cx, |editor, cx| {
5520 let pending_mouse_down = editor
5521 .pending_mouse_down
5522 .get_or_insert_with(Default::default)
5523 .clone();
5524
5525 *pending_mouse_down.borrow_mut() = Some(event.clone());
5526
5527 Self::mouse_left_down(
5528 editor,
5529 event,
5530 diff_hunk_range.clone(),
5531 &position_map,
5532 line_numbers.as_ref(),
5533 window,
5534 cx,
5535 );
5536 }),
5537 MouseButton::Right => editor.update(cx, |editor, cx| {
5538 Self::mouse_right_down(editor, event, &position_map, window, cx);
5539 }),
5540 MouseButton::Middle => editor.update(cx, |editor, cx| {
5541 Self::mouse_middle_down(editor, event, &position_map, window, cx);
5542 }),
5543 _ => {}
5544 };
5545 }
5546 }
5547 });
5548
5549 window.on_mouse_event({
5550 let editor = self.editor.clone();
5551 let position_map = layout.position_map.clone();
5552
5553 move |event: &MouseUpEvent, phase, window, cx| {
5554 if phase == DispatchPhase::Bubble {
5555 editor.update(cx, |editor, cx| {
5556 Self::mouse_up(editor, event, &position_map, window, cx)
5557 });
5558 }
5559 }
5560 });
5561
5562 window.on_mouse_event({
5563 let editor = self.editor.clone();
5564 let position_map = layout.position_map.clone();
5565 let mut captured_mouse_down = None;
5566
5567 move |event: &MouseUpEvent, phase, window, cx| match phase {
5568 // Clear the pending mouse down during the capture phase,
5569 // so that it happens even if another event handler stops
5570 // propagation.
5571 DispatchPhase::Capture => editor.update(cx, |editor, _cx| {
5572 let pending_mouse_down = editor
5573 .pending_mouse_down
5574 .get_or_insert_with(Default::default)
5575 .clone();
5576
5577 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
5578 if pending_mouse_down.is_some() && position_map.text_hitbox.is_hovered(window) {
5579 captured_mouse_down = pending_mouse_down.take();
5580 window.refresh();
5581 }
5582 }),
5583 // Fire click handlers during the bubble phase.
5584 DispatchPhase::Bubble => editor.update(cx, |editor, cx| {
5585 if let Some(mouse_down) = captured_mouse_down.take() {
5586 let event = ClickEvent {
5587 down: mouse_down,
5588 up: event.clone(),
5589 };
5590 Self::click(editor, &event, &position_map, window, cx);
5591 }
5592 }),
5593 }
5594 });
5595
5596 window.on_mouse_event({
5597 let position_map = layout.position_map.clone();
5598 let editor = self.editor.clone();
5599
5600 move |event: &MouseMoveEvent, phase, window, cx| {
5601 if phase == DispatchPhase::Bubble {
5602 editor.update(cx, |editor, cx| {
5603 if editor.hover_state.focused(window, cx) {
5604 return;
5605 }
5606 if event.pressed_button == Some(MouseButton::Left)
5607 || event.pressed_button == Some(MouseButton::Middle)
5608 {
5609 Self::mouse_dragged(editor, event, &position_map, window, cx)
5610 }
5611
5612 Self::mouse_moved(editor, event, &position_map, window, cx)
5613 });
5614 }
5615 }
5616 });
5617 }
5618
5619 fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
5620 bounds.top_right().x - self.style.scrollbar_width
5621 }
5622
5623 fn column_pixels(&self, column: usize, window: &mut Window, _: &mut App) -> Pixels {
5624 let style = &self.style;
5625 let font_size = style.text.font_size.to_pixels(window.rem_size());
5626 let layout = window
5627 .text_system()
5628 .shape_line(
5629 SharedString::from(" ".repeat(column)),
5630 font_size,
5631 &[TextRun {
5632 len: column,
5633 font: style.text.font(),
5634 color: Hsla::default(),
5635 background_color: None,
5636 underline: None,
5637 strikethrough: None,
5638 }],
5639 )
5640 .unwrap();
5641
5642 layout.width
5643 }
5644
5645 fn max_line_number_width(
5646 &self,
5647 snapshot: &EditorSnapshot,
5648 window: &mut Window,
5649 cx: &mut App,
5650 ) -> Pixels {
5651 let digit_count = snapshot.widest_line_number().ilog10() + 1;
5652 self.column_pixels(digit_count as usize, window, cx)
5653 }
5654
5655 fn shape_line_number(
5656 &self,
5657 text: SharedString,
5658 color: Hsla,
5659 window: &mut Window,
5660 ) -> anyhow::Result<ShapedLine> {
5661 let run = TextRun {
5662 len: text.len(),
5663 font: self.style.text.font(),
5664 color,
5665 background_color: None,
5666 underline: None,
5667 strikethrough: None,
5668 };
5669 window.text_system().shape_line(
5670 text,
5671 self.style.text.font_size.to_pixels(window.rem_size()),
5672 &[run],
5673 )
5674 }
5675
5676 fn diff_hunk_hollow(status: DiffHunkStatus, cx: &mut App) -> bool {
5677 let unstaged = status.has_secondary_hunk();
5678 let unstaged_hollow = ProjectSettings::get_global(cx)
5679 .git
5680 .hunk_style
5681 .map_or(false, |style| {
5682 matches!(style, GitHunkStyleSetting::UnstagedHollow)
5683 });
5684
5685 unstaged == unstaged_hollow
5686 }
5687}
5688
5689fn header_jump_data(
5690 snapshot: &EditorSnapshot,
5691 block_row_start: DisplayRow,
5692 height: u32,
5693 for_excerpt: &ExcerptInfo,
5694) -> JumpData {
5695 let range = &for_excerpt.range;
5696 let buffer = &for_excerpt.buffer;
5697 let jump_anchor = range.primary.start;
5698
5699 let excerpt_start = range.context.start;
5700 let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
5701 let rows_from_excerpt_start = if jump_anchor == excerpt_start {
5702 0
5703 } else {
5704 let excerpt_start_point = language::ToPoint::to_point(&excerpt_start, buffer);
5705 jump_position.row.saturating_sub(excerpt_start_point.row)
5706 };
5707
5708 let line_offset_from_top = (block_row_start.0 + height + rows_from_excerpt_start)
5709 .saturating_sub(
5710 snapshot
5711 .scroll_anchor
5712 .scroll_position(&snapshot.display_snapshot)
5713 .y as u32,
5714 );
5715
5716 JumpData::MultiBufferPoint {
5717 excerpt_id: for_excerpt.id,
5718 anchor: jump_anchor,
5719 position: jump_position,
5720 line_offset_from_top,
5721 }
5722}
5723
5724pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);
5725
5726impl AcceptEditPredictionBinding {
5727 pub fn keystroke(&self) -> Option<&Keystroke> {
5728 if let Some(binding) = self.0.as_ref() {
5729 match &binding.keystrokes() {
5730 [keystroke] => Some(keystroke),
5731 _ => None,
5732 }
5733 } else {
5734 None
5735 }
5736 }
5737}
5738
5739fn prepaint_gutter_button(
5740 button: IconButton,
5741 row: DisplayRow,
5742 line_height: Pixels,
5743 gutter_dimensions: &GutterDimensions,
5744 scroll_pixel_position: gpui::Point<Pixels>,
5745 gutter_hitbox: &Hitbox,
5746 display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
5747 window: &mut Window,
5748 cx: &mut App,
5749) -> AnyElement {
5750 let mut button = button.into_any_element();
5751
5752 let available_space = size(
5753 AvailableSpace::MinContent,
5754 AvailableSpace::Definite(line_height),
5755 );
5756 let indicator_size = button.layout_as_root(available_space, window, cx);
5757
5758 let blame_width = gutter_dimensions.git_blame_entries_width;
5759 let gutter_width = display_hunks
5760 .binary_search_by(|(hunk, _)| match hunk {
5761 DisplayDiffHunk::Folded { display_row } => display_row.cmp(&row),
5762 DisplayDiffHunk::Unfolded {
5763 display_row_range, ..
5764 } => {
5765 if display_row_range.end <= row {
5766 Ordering::Less
5767 } else if display_row_range.start > row {
5768 Ordering::Greater
5769 } else {
5770 Ordering::Equal
5771 }
5772 }
5773 })
5774 .ok()
5775 .and_then(|ix| Some(display_hunks[ix].1.as_ref()?.size.width));
5776 let left_offset = blame_width.max(gutter_width).unwrap_or_default();
5777
5778 let mut x = left_offset;
5779 let available_width = gutter_dimensions.margin + gutter_dimensions.left_padding
5780 - indicator_size.width
5781 - left_offset;
5782 x += available_width / 2.;
5783
5784 let mut y = row.as_f32() * line_height - scroll_pixel_position.y;
5785 y += (line_height - indicator_size.height) / 2.;
5786
5787 button.prepaint_as_root(
5788 gutter_hitbox.origin + point(x, y),
5789 available_space,
5790 window,
5791 cx,
5792 );
5793 button
5794}
5795
5796fn render_inline_blame_entry(
5797 editor: Entity<Editor>,
5798 workspace: WeakEntity<Workspace>,
5799 blame: &Entity<GitBlame>,
5800 blame_entry: BlameEntry,
5801 style: &EditorStyle,
5802 cx: &mut App,
5803) -> Option<AnyElement> {
5804 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
5805 let blame = blame.read(cx);
5806 let details = blame.details_for_entry(&blame_entry);
5807 let repository = blame.repository(cx)?.clone();
5808 renderer.render_inline_blame_entry(
5809 &style.text,
5810 blame_entry,
5811 details,
5812 repository,
5813 workspace,
5814 editor,
5815 cx,
5816 )
5817}
5818
5819fn render_blame_entry(
5820 ix: usize,
5821 blame: &Entity<GitBlame>,
5822 blame_entry: BlameEntry,
5823 style: &EditorStyle,
5824 last_used_color: &mut Option<(PlayerColor, Oid)>,
5825 editor: Entity<Editor>,
5826 workspace: Entity<Workspace>,
5827 renderer: Arc<dyn BlameRenderer>,
5828 cx: &mut App,
5829) -> Option<AnyElement> {
5830 let mut sha_color = cx
5831 .theme()
5832 .players()
5833 .color_for_participant(blame_entry.sha.into());
5834
5835 // If the last color we used is the same as the one we get for this line, but
5836 // the commit SHAs are different, then we try again to get a different color.
5837 match *last_used_color {
5838 Some((color, sha)) if sha != blame_entry.sha && color.cursor == sha_color.cursor => {
5839 let index: u32 = blame_entry.sha.into();
5840 sha_color = cx.theme().players().color_for_participant(index + 1);
5841 }
5842 _ => {}
5843 };
5844 last_used_color.replace((sha_color, blame_entry.sha));
5845
5846 let blame = blame.read(cx);
5847 let details = blame.details_for_entry(&blame_entry);
5848 let repository = blame.repository(cx)?;
5849 renderer.render_blame_entry(
5850 &style.text,
5851 blame_entry,
5852 details,
5853 repository,
5854 workspace.downgrade(),
5855 editor,
5856 ix,
5857 sha_color.cursor,
5858 cx,
5859 )
5860}
5861
5862#[derive(Debug)]
5863pub(crate) struct LineWithInvisibles {
5864 fragments: SmallVec<[LineFragment; 1]>,
5865 invisibles: Vec<Invisible>,
5866 len: usize,
5867 pub(crate) width: Pixels,
5868 font_size: Pixels,
5869}
5870
5871#[allow(clippy::large_enum_variant)]
5872enum LineFragment {
5873 Text(ShapedLine),
5874 Element {
5875 id: FoldId,
5876 element: Option<AnyElement>,
5877 size: Size<Pixels>,
5878 len: usize,
5879 },
5880}
5881
5882impl fmt::Debug for LineFragment {
5883 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5884 match self {
5885 LineFragment::Text(shaped_line) => f.debug_tuple("Text").field(shaped_line).finish(),
5886 LineFragment::Element { size, len, .. } => f
5887 .debug_struct("Element")
5888 .field("size", size)
5889 .field("len", len)
5890 .finish(),
5891 }
5892 }
5893}
5894
5895impl LineWithInvisibles {
5896 fn from_chunks<'a>(
5897 chunks: impl Iterator<Item = HighlightedChunk<'a>>,
5898 editor_style: &EditorStyle,
5899 max_line_len: usize,
5900 max_line_count: usize,
5901 editor_mode: EditorMode,
5902 text_width: Pixels,
5903 is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
5904 window: &mut Window,
5905 cx: &mut App,
5906 ) -> Vec<Self> {
5907 let text_style = &editor_style.text;
5908 let mut layouts = Vec::with_capacity(max_line_count);
5909 let mut fragments: SmallVec<[LineFragment; 1]> = SmallVec::new();
5910 let mut line = String::new();
5911 let mut invisibles = Vec::new();
5912 let mut width = Pixels::ZERO;
5913 let mut len = 0;
5914 let mut styles = Vec::new();
5915 let mut non_whitespace_added = false;
5916 let mut row = 0;
5917 let mut line_exceeded_max_len = false;
5918 let font_size = text_style.font_size.to_pixels(window.rem_size());
5919
5920 let ellipsis = SharedString::from("⋯");
5921
5922 for highlighted_chunk in chunks.chain([HighlightedChunk {
5923 text: "\n",
5924 style: None,
5925 is_tab: false,
5926 replacement: None,
5927 }]) {
5928 if let Some(replacement) = highlighted_chunk.replacement {
5929 if !line.is_empty() {
5930 let shaped_line = window
5931 .text_system()
5932 .shape_line(line.clone().into(), font_size, &styles)
5933 .unwrap();
5934 width += shaped_line.width;
5935 len += shaped_line.len;
5936 fragments.push(LineFragment::Text(shaped_line));
5937 line.clear();
5938 styles.clear();
5939 }
5940
5941 match replacement {
5942 ChunkReplacement::Renderer(renderer) => {
5943 let available_width = if renderer.constrain_width {
5944 let chunk = if highlighted_chunk.text == ellipsis.as_ref() {
5945 ellipsis.clone()
5946 } else {
5947 SharedString::from(Arc::from(highlighted_chunk.text))
5948 };
5949 let shaped_line = window
5950 .text_system()
5951 .shape_line(
5952 chunk,
5953 font_size,
5954 &[text_style.to_run(highlighted_chunk.text.len())],
5955 )
5956 .unwrap();
5957 AvailableSpace::Definite(shaped_line.width)
5958 } else {
5959 AvailableSpace::MinContent
5960 };
5961
5962 let mut element = (renderer.render)(&mut ChunkRendererContext {
5963 context: cx,
5964 window,
5965 max_width: text_width,
5966 });
5967 let line_height = text_style.line_height_in_pixels(window.rem_size());
5968 let size = element.layout_as_root(
5969 size(available_width, AvailableSpace::Definite(line_height)),
5970 window,
5971 cx,
5972 );
5973
5974 width += size.width;
5975 len += highlighted_chunk.text.len();
5976 fragments.push(LineFragment::Element {
5977 id: renderer.id,
5978 element: Some(element),
5979 size,
5980 len: highlighted_chunk.text.len(),
5981 });
5982 }
5983 ChunkReplacement::Str(x) => {
5984 let text_style = if let Some(style) = highlighted_chunk.style {
5985 Cow::Owned(text_style.clone().highlight(style))
5986 } else {
5987 Cow::Borrowed(text_style)
5988 };
5989
5990 let run = TextRun {
5991 len: x.len(),
5992 font: text_style.font(),
5993 color: text_style.color,
5994 background_color: text_style.background_color,
5995 underline: text_style.underline,
5996 strikethrough: text_style.strikethrough,
5997 };
5998 let line_layout = window
5999 .text_system()
6000 .shape_line(x, font_size, &[run])
6001 .unwrap()
6002 .with_len(highlighted_chunk.text.len());
6003
6004 width += line_layout.width;
6005 len += highlighted_chunk.text.len();
6006 fragments.push(LineFragment::Text(line_layout))
6007 }
6008 }
6009 } else {
6010 for (ix, mut line_chunk) in highlighted_chunk.text.split('\n').enumerate() {
6011 if ix > 0 {
6012 let shaped_line = window
6013 .text_system()
6014 .shape_line(line.clone().into(), font_size, &styles)
6015 .unwrap();
6016 width += shaped_line.width;
6017 len += shaped_line.len;
6018 fragments.push(LineFragment::Text(shaped_line));
6019 layouts.push(Self {
6020 width: mem::take(&mut width),
6021 len: mem::take(&mut len),
6022 fragments: mem::take(&mut fragments),
6023 invisibles: std::mem::take(&mut invisibles),
6024 font_size,
6025 });
6026
6027 line.clear();
6028 styles.clear();
6029 row += 1;
6030 line_exceeded_max_len = false;
6031 non_whitespace_added = false;
6032 if row == max_line_count {
6033 return layouts;
6034 }
6035 }
6036
6037 if !line_chunk.is_empty() && !line_exceeded_max_len {
6038 let text_style = if let Some(style) = highlighted_chunk.style {
6039 Cow::Owned(text_style.clone().highlight(style))
6040 } else {
6041 Cow::Borrowed(text_style)
6042 };
6043
6044 if line.len() + line_chunk.len() > max_line_len {
6045 let mut chunk_len = max_line_len - line.len();
6046 while !line_chunk.is_char_boundary(chunk_len) {
6047 chunk_len -= 1;
6048 }
6049 line_chunk = &line_chunk[..chunk_len];
6050 line_exceeded_max_len = true;
6051 }
6052
6053 styles.push(TextRun {
6054 len: line_chunk.len(),
6055 font: text_style.font(),
6056 color: text_style.color,
6057 background_color: text_style.background_color,
6058 underline: text_style.underline,
6059 strikethrough: text_style.strikethrough,
6060 });
6061
6062 if editor_mode.is_full() {
6063 // Line wrap pads its contents with fake whitespaces,
6064 // avoid printing them
6065 let is_soft_wrapped = is_row_soft_wrapped(row);
6066 if highlighted_chunk.is_tab {
6067 if non_whitespace_added || !is_soft_wrapped {
6068 invisibles.push(Invisible::Tab {
6069 line_start_offset: line.len(),
6070 line_end_offset: line.len() + line_chunk.len(),
6071 });
6072 }
6073 } else {
6074 invisibles.extend(line_chunk.char_indices().filter_map(
6075 |(index, c)| {
6076 let is_whitespace = c.is_whitespace();
6077 non_whitespace_added |= !is_whitespace;
6078 if is_whitespace
6079 && (non_whitespace_added || !is_soft_wrapped)
6080 {
6081 Some(Invisible::Whitespace {
6082 line_offset: line.len() + index,
6083 })
6084 } else {
6085 None
6086 }
6087 },
6088 ))
6089 }
6090 }
6091
6092 line.push_str(line_chunk);
6093 }
6094 }
6095 }
6096 }
6097
6098 layouts
6099 }
6100
6101 fn prepaint(
6102 &mut self,
6103 line_height: Pixels,
6104 scroll_pixel_position: gpui::Point<Pixels>,
6105 row: DisplayRow,
6106 content_origin: gpui::Point<Pixels>,
6107 line_elements: &mut SmallVec<[AnyElement; 1]>,
6108 window: &mut Window,
6109 cx: &mut App,
6110 ) {
6111 let line_y = line_height * (row.as_f32() - scroll_pixel_position.y / line_height);
6112 let mut fragment_origin = content_origin + gpui::point(-scroll_pixel_position.x, line_y);
6113 for fragment in &mut self.fragments {
6114 match fragment {
6115 LineFragment::Text(line) => {
6116 fragment_origin.x += line.width;
6117 }
6118 LineFragment::Element { element, size, .. } => {
6119 let mut element = element
6120 .take()
6121 .expect("you can't prepaint LineWithInvisibles twice");
6122
6123 // Center the element vertically within the line.
6124 let mut element_origin = fragment_origin;
6125 element_origin.y += (line_height - size.height) / 2.;
6126 element.prepaint_at(element_origin, window, cx);
6127 line_elements.push(element);
6128
6129 fragment_origin.x += size.width;
6130 }
6131 }
6132 }
6133 }
6134
6135 fn draw(
6136 &self,
6137 layout: &EditorLayout,
6138 row: DisplayRow,
6139 content_origin: gpui::Point<Pixels>,
6140 whitespace_setting: ShowWhitespaceSetting,
6141 selection_ranges: &[Range<DisplayPoint>],
6142 window: &mut Window,
6143 cx: &mut App,
6144 ) {
6145 let line_height = layout.position_map.line_height;
6146 let line_y = line_height
6147 * (row.as_f32() - layout.position_map.scroll_pixel_position.y / line_height);
6148
6149 let mut fragment_origin =
6150 content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y);
6151
6152 for fragment in &self.fragments {
6153 match fragment {
6154 LineFragment::Text(line) => {
6155 line.paint(fragment_origin, line_height, window, cx)
6156 .log_err();
6157 fragment_origin.x += line.width;
6158 }
6159 LineFragment::Element { size, .. } => {
6160 fragment_origin.x += size.width;
6161 }
6162 }
6163 }
6164
6165 self.draw_invisibles(
6166 selection_ranges,
6167 layout,
6168 content_origin,
6169 line_y,
6170 row,
6171 line_height,
6172 whitespace_setting,
6173 window,
6174 cx,
6175 );
6176 }
6177
6178 fn draw_background(
6179 &self,
6180 layout: &EditorLayout,
6181 row: DisplayRow,
6182 content_origin: gpui::Point<Pixels>,
6183 window: &mut Window,
6184 cx: &mut App,
6185 ) {
6186 let line_height = layout.position_map.line_height;
6187 let line_y = line_height
6188 * (row.as_f32() - layout.position_map.scroll_pixel_position.y / line_height);
6189
6190 let mut fragment_origin =
6191 content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y);
6192
6193 for fragment in &self.fragments {
6194 match fragment {
6195 LineFragment::Text(line) => {
6196 line.paint_background(fragment_origin, line_height, window, cx)
6197 .log_err();
6198 fragment_origin.x += line.width;
6199 }
6200 LineFragment::Element { size, .. } => {
6201 fragment_origin.x += size.width;
6202 }
6203 }
6204 }
6205 }
6206
6207 fn draw_invisibles(
6208 &self,
6209 selection_ranges: &[Range<DisplayPoint>],
6210 layout: &EditorLayout,
6211 content_origin: gpui::Point<Pixels>,
6212 line_y: Pixels,
6213 row: DisplayRow,
6214 line_height: Pixels,
6215 whitespace_setting: ShowWhitespaceSetting,
6216 window: &mut Window,
6217 cx: &mut App,
6218 ) {
6219 let extract_whitespace_info = |invisible: &Invisible| {
6220 let (token_offset, token_end_offset, invisible_symbol) = match invisible {
6221 Invisible::Tab {
6222 line_start_offset,
6223 line_end_offset,
6224 } => (*line_start_offset, *line_end_offset, &layout.tab_invisible),
6225 Invisible::Whitespace { line_offset } => {
6226 (*line_offset, line_offset + 1, &layout.space_invisible)
6227 }
6228 };
6229
6230 let x_offset = self.x_for_index(token_offset);
6231 let invisible_offset =
6232 (layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
6233 let origin = content_origin
6234 + gpui::point(
6235 x_offset + invisible_offset - layout.position_map.scroll_pixel_position.x,
6236 line_y,
6237 );
6238
6239 (
6240 [token_offset, token_end_offset],
6241 Box::new(move |window: &mut Window, cx: &mut App| {
6242 invisible_symbol
6243 .paint(origin, line_height, window, cx)
6244 .log_err();
6245 }),
6246 )
6247 };
6248
6249 let invisible_iter = self.invisibles.iter().map(extract_whitespace_info);
6250 match whitespace_setting {
6251 ShowWhitespaceSetting::None => (),
6252 ShowWhitespaceSetting::All => invisible_iter.for_each(|(_, paint)| paint(window, cx)),
6253 ShowWhitespaceSetting::Selection => invisible_iter.for_each(|([start, _], paint)| {
6254 let invisible_point = DisplayPoint::new(row, start as u32);
6255 if !selection_ranges
6256 .iter()
6257 .any(|region| region.start <= invisible_point && invisible_point < region.end)
6258 {
6259 return;
6260 }
6261
6262 paint(window, cx);
6263 }),
6264
6265 // For a whitespace to be on a boundary, any of the following conditions need to be met:
6266 // - It is a tab
6267 // - It is adjacent to an edge (start or end)
6268 // - It is adjacent to a whitespace (left or right)
6269 ShowWhitespaceSetting::Boundary => {
6270 // 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
6271 // the above cases.
6272 // Note: We zip in the original `invisibles` to check for tab equality
6273 let mut last_seen: Option<(bool, usize, Box<dyn Fn(&mut Window, &mut App)>)> = None;
6274 for (([start, end], paint), invisible) in
6275 invisible_iter.zip_eq(self.invisibles.iter())
6276 {
6277 let should_render = match (&last_seen, invisible) {
6278 (_, Invisible::Tab { .. }) => true,
6279 (Some((_, last_end, _)), _) => *last_end == start,
6280 _ => false,
6281 };
6282
6283 if should_render || start == 0 || end == self.len {
6284 paint(window, cx);
6285
6286 // Since we are scanning from the left, we will skip over the first available whitespace that is part
6287 // of a boundary between non-whitespace segments, so we correct by manually redrawing it if needed.
6288 if let Some((should_render_last, last_end, paint_last)) = last_seen {
6289 // Note that we need to make sure that the last one is actually adjacent
6290 if !should_render_last && last_end == start {
6291 paint_last(window, cx);
6292 }
6293 }
6294 }
6295
6296 // Manually render anything within a selection
6297 let invisible_point = DisplayPoint::new(row, start as u32);
6298 if selection_ranges.iter().any(|region| {
6299 region.start <= invisible_point && invisible_point < region.end
6300 }) {
6301 paint(window, cx);
6302 }
6303
6304 last_seen = Some((should_render, end, paint));
6305 }
6306 }
6307 }
6308 }
6309
6310 pub fn x_for_index(&self, index: usize) -> Pixels {
6311 let mut fragment_start_x = Pixels::ZERO;
6312 let mut fragment_start_index = 0;
6313
6314 for fragment in &self.fragments {
6315 match fragment {
6316 LineFragment::Text(shaped_line) => {
6317 let fragment_end_index = fragment_start_index + shaped_line.len;
6318 if index < fragment_end_index {
6319 return fragment_start_x
6320 + shaped_line.x_for_index(index - fragment_start_index);
6321 }
6322 fragment_start_x += shaped_line.width;
6323 fragment_start_index = fragment_end_index;
6324 }
6325 LineFragment::Element { len, size, .. } => {
6326 let fragment_end_index = fragment_start_index + len;
6327 if index < fragment_end_index {
6328 return fragment_start_x;
6329 }
6330 fragment_start_x += size.width;
6331 fragment_start_index = fragment_end_index;
6332 }
6333 }
6334 }
6335
6336 fragment_start_x
6337 }
6338
6339 pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
6340 let mut fragment_start_x = Pixels::ZERO;
6341 let mut fragment_start_index = 0;
6342
6343 for fragment in &self.fragments {
6344 match fragment {
6345 LineFragment::Text(shaped_line) => {
6346 let fragment_end_x = fragment_start_x + shaped_line.width;
6347 if x < fragment_end_x {
6348 return Some(
6349 fragment_start_index + shaped_line.index_for_x(x - fragment_start_x)?,
6350 );
6351 }
6352 fragment_start_x = fragment_end_x;
6353 fragment_start_index += shaped_line.len;
6354 }
6355 LineFragment::Element { len, size, .. } => {
6356 let fragment_end_x = fragment_start_x + size.width;
6357 if x < fragment_end_x {
6358 return Some(fragment_start_index);
6359 }
6360 fragment_start_index += len;
6361 fragment_start_x = fragment_end_x;
6362 }
6363 }
6364 }
6365
6366 None
6367 }
6368
6369 pub fn font_id_for_index(&self, index: usize) -> Option<FontId> {
6370 let mut fragment_start_index = 0;
6371
6372 for fragment in &self.fragments {
6373 match fragment {
6374 LineFragment::Text(shaped_line) => {
6375 let fragment_end_index = fragment_start_index + shaped_line.len;
6376 if index < fragment_end_index {
6377 return shaped_line.font_id_for_index(index - fragment_start_index);
6378 }
6379 fragment_start_index = fragment_end_index;
6380 }
6381 LineFragment::Element { len, .. } => {
6382 let fragment_end_index = fragment_start_index + len;
6383 if index < fragment_end_index {
6384 return None;
6385 }
6386 fragment_start_index = fragment_end_index;
6387 }
6388 }
6389 }
6390
6391 None
6392 }
6393}
6394
6395#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6396enum Invisible {
6397 /// A tab character
6398 ///
6399 /// A tab character is internally represented by spaces (configured by the user's tab width)
6400 /// aligned to the nearest column, so it's necessary to store the start and end offset for
6401 /// adjacency checks.
6402 Tab {
6403 line_start_offset: usize,
6404 line_end_offset: usize,
6405 },
6406 Whitespace {
6407 line_offset: usize,
6408 },
6409}
6410
6411impl EditorElement {
6412 /// Returns the rem size to use when rendering the [`EditorElement`].
6413 ///
6414 /// This allows UI elements to scale based on the `buffer_font_size`.
6415 fn rem_size(&self, cx: &mut App) -> Option<Pixels> {
6416 match self.editor.read(cx).mode {
6417 EditorMode::Full { .. } => {
6418 let buffer_font_size = self.style.text.font_size;
6419 match buffer_font_size {
6420 AbsoluteLength::Pixels(pixels) => {
6421 let rem_size_scale = {
6422 // Our default UI font size is 14px on a 16px base scale.
6423 // This means the default UI font size is 0.875rems.
6424 let default_font_size_scale = 14. / ui::BASE_REM_SIZE_IN_PX;
6425
6426 // We then determine the delta between a single rem and the default font
6427 // size scale.
6428 let default_font_size_delta = 1. - default_font_size_scale;
6429
6430 // Finally, we add this delta to 1rem to get the scale factor that
6431 // should be used to scale up the UI.
6432 1. + default_font_size_delta
6433 };
6434
6435 Some(pixels * rem_size_scale)
6436 }
6437 AbsoluteLength::Rems(rems) => {
6438 Some(rems.to_pixels(ui::BASE_REM_SIZE_IN_PX.into()))
6439 }
6440 }
6441 }
6442 // We currently use single-line and auto-height editors in UI contexts,
6443 // so we don't want to scale everything with the buffer font size, as it
6444 // ends up looking off.
6445 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => None,
6446 }
6447 }
6448}
6449
6450impl Element for EditorElement {
6451 type RequestLayoutState = ();
6452 type PrepaintState = EditorLayout;
6453
6454 fn id(&self) -> Option<ElementId> {
6455 None
6456 }
6457
6458 fn request_layout(
6459 &mut self,
6460 _: Option<&GlobalElementId>,
6461 window: &mut Window,
6462 cx: &mut App,
6463 ) -> (gpui::LayoutId, ()) {
6464 let rem_size = self.rem_size(cx);
6465 window.with_rem_size(rem_size, |window| {
6466 self.editor.update(cx, |editor, cx| {
6467 editor.set_style(self.style.clone(), window, cx);
6468
6469 let layout_id = match editor.mode {
6470 EditorMode::SingleLine { auto_width } => {
6471 let rem_size = window.rem_size();
6472
6473 let height = self.style.text.line_height_in_pixels(rem_size);
6474 if auto_width {
6475 let editor_handle = cx.entity().clone();
6476 let style = self.style.clone();
6477 window.request_measured_layout(
6478 Style::default(),
6479 move |_, _, window, cx| {
6480 let editor_snapshot = editor_handle
6481 .update(cx, |editor, cx| editor.snapshot(window, cx));
6482 let line = Self::layout_lines(
6483 DisplayRow(0)..DisplayRow(1),
6484 &editor_snapshot,
6485 &style,
6486 px(f32::MAX),
6487 |_| false, // Single lines never soft wrap
6488 window,
6489 cx,
6490 )
6491 .pop()
6492 .unwrap();
6493
6494 let font_id =
6495 window.text_system().resolve_font(&style.text.font());
6496 let font_size =
6497 style.text.font_size.to_pixels(window.rem_size());
6498 let em_width =
6499 window.text_system().em_width(font_id, font_size).unwrap();
6500
6501 size(line.width + em_width, height)
6502 },
6503 )
6504 } else {
6505 let mut style = Style::default();
6506 style.size.height = height.into();
6507 style.size.width = relative(1.).into();
6508 window.request_layout(style, None, cx)
6509 }
6510 }
6511 EditorMode::AutoHeight { max_lines } => {
6512 let editor_handle = cx.entity().clone();
6513 let max_line_number_width =
6514 self.max_line_number_width(&editor.snapshot(window, cx), window, cx);
6515 window.request_measured_layout(
6516 Style::default(),
6517 move |known_dimensions, available_space, window, cx| {
6518 editor_handle
6519 .update(cx, |editor, cx| {
6520 compute_auto_height_layout(
6521 editor,
6522 max_lines,
6523 max_line_number_width,
6524 known_dimensions,
6525 available_space.width,
6526 window,
6527 cx,
6528 )
6529 })
6530 .unwrap_or_default()
6531 },
6532 )
6533 }
6534 EditorMode::Full { .. } => {
6535 let mut style = Style::default();
6536 style.size.width = relative(1.).into();
6537 style.size.height = relative(1.).into();
6538 window.request_layout(style, None, cx)
6539 }
6540 };
6541
6542 (layout_id, ())
6543 })
6544 })
6545 }
6546
6547 fn prepaint(
6548 &mut self,
6549 _: Option<&GlobalElementId>,
6550 bounds: Bounds<Pixels>,
6551 _: &mut Self::RequestLayoutState,
6552 window: &mut Window,
6553 cx: &mut App,
6554 ) -> Self::PrepaintState {
6555 let text_style = TextStyleRefinement {
6556 font_size: Some(self.style.text.font_size),
6557 line_height: Some(self.style.text.line_height),
6558 ..Default::default()
6559 };
6560 let focus_handle = self.editor.focus_handle(cx);
6561 window.set_view_id(self.editor.entity_id());
6562 window.set_focus_handle(&focus_handle, cx);
6563
6564 let rem_size = self.rem_size(cx);
6565 window.with_rem_size(rem_size, |window| {
6566 window.with_text_style(Some(text_style), |window| {
6567 window.with_content_mask(Some(ContentMask { bounds }), |window| {
6568 let (mut snapshot, is_read_only) = self.editor.update(cx, |editor, cx| {
6569 (editor.snapshot(window, cx), editor.read_only(cx))
6570 });
6571 let style = self.style.clone();
6572
6573 let font_id = window.text_system().resolve_font(&style.text.font());
6574 let font_size = style.text.font_size.to_pixels(window.rem_size());
6575 let line_height = style.text.line_height_in_pixels(window.rem_size());
6576 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
6577 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
6578
6579 let glyph_grid_cell = size(em_width, line_height);
6580
6581 let gutter_dimensions = snapshot
6582 .gutter_dimensions(
6583 font_id,
6584 font_size,
6585 self.max_line_number_width(&snapshot, window, cx),
6586 cx,
6587 )
6588 .unwrap_or_default();
6589 let text_width = bounds.size.width - gutter_dimensions.width;
6590
6591 let editor_width =
6592 text_width - gutter_dimensions.margin - em_width - style.scrollbar_width;
6593
6594 snapshot = self.editor.update(cx, |editor, cx| {
6595 editor.last_bounds = Some(bounds);
6596 editor.gutter_dimensions = gutter_dimensions;
6597 editor.set_visible_line_count(bounds.size.height / line_height, window, cx);
6598
6599 if matches!(editor.mode, EditorMode::AutoHeight { .. }) {
6600 snapshot
6601 } else {
6602 let wrap_width = match editor.soft_wrap_mode(cx) {
6603 SoftWrap::GitDiff => None,
6604 SoftWrap::None => Some((MAX_LINE_LEN / 2) as f32 * em_advance),
6605 SoftWrap::EditorWidth => Some(editor_width),
6606 SoftWrap::Column(column) => Some(column as f32 * em_advance),
6607 SoftWrap::Bounded(column) => {
6608 Some(editor_width.min(column as f32 * em_advance))
6609 }
6610 };
6611
6612 if editor.set_wrap_width(wrap_width, cx) {
6613 editor.snapshot(window, cx)
6614 } else {
6615 snapshot
6616 }
6617 }
6618 });
6619
6620 let wrap_guides = self
6621 .editor
6622 .read(cx)
6623 .wrap_guides(cx)
6624 .iter()
6625 .map(|(guide, active)| (self.column_pixels(*guide, window, cx), *active))
6626 .collect::<SmallVec<[_; 2]>>();
6627
6628 let hitbox = window.insert_hitbox(bounds, false);
6629 let gutter_hitbox =
6630 window.insert_hitbox(gutter_bounds(bounds, gutter_dimensions), false);
6631 let text_hitbox = window.insert_hitbox(
6632 Bounds {
6633 origin: gutter_hitbox.top_right(),
6634 size: size(text_width, bounds.size.height),
6635 },
6636 false,
6637 );
6638
6639 // Offset the content_bounds from the text_bounds by the gutter margin (which
6640 // is roughly half a character wide) to make hit testing work more like how we want.
6641 let content_offset = point(gutter_dimensions.margin, Pixels::ZERO);
6642 let content_origin = text_hitbox.origin + content_offset;
6643
6644 let editor_text_bounds =
6645 Bounds::from_corners(content_origin, bounds.bottom_right());
6646
6647 let height_in_lines = editor_text_bounds.size.height / line_height;
6648
6649 let max_row = snapshot.max_point().row().as_f32();
6650
6651 // The max scroll position for the top of the window
6652 let max_scroll_top = if matches!(snapshot.mode, EditorMode::AutoHeight { .. }) {
6653 (max_row - height_in_lines + 1.).max(0.)
6654 } else {
6655 let settings = EditorSettings::get_global(cx);
6656 match settings.scroll_beyond_last_line {
6657 ScrollBeyondLastLine::OnePage => max_row,
6658 ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.).max(0.),
6659 ScrollBeyondLastLine::VerticalScrollMargin => {
6660 (max_row - height_in_lines + 1. + settings.vertical_scroll_margin)
6661 .max(0.)
6662 }
6663 }
6664 };
6665
6666 // TODO: Autoscrolling for both axes
6667 let mut autoscroll_request = None;
6668 let mut autoscroll_containing_element = false;
6669 let mut autoscroll_horizontally = false;
6670 self.editor.update(cx, |editor, cx| {
6671 autoscroll_request = editor.autoscroll_request();
6672 autoscroll_containing_element =
6673 autoscroll_request.is_some() || editor.has_pending_selection();
6674 // TODO: Is this horizontal or vertical?!
6675 autoscroll_horizontally = editor.autoscroll_vertically(
6676 bounds,
6677 line_height,
6678 max_scroll_top,
6679 window,
6680 cx,
6681 );
6682 snapshot = editor.snapshot(window, cx);
6683 });
6684
6685 let mut scroll_position = snapshot.scroll_position();
6686 // The scroll position is a fractional point, the whole number of which represents
6687 // the top of the window in terms of display rows.
6688 let start_row = DisplayRow(scroll_position.y as u32);
6689 let max_row = snapshot.max_point().row();
6690 let end_row = cmp::min(
6691 (scroll_position.y + height_in_lines).ceil() as u32,
6692 max_row.next_row().0,
6693 );
6694 let end_row = DisplayRow(end_row);
6695
6696 let row_infos = snapshot
6697 .row_infos(start_row)
6698 .take((start_row..end_row).len())
6699 .collect::<Vec<RowInfo>>();
6700 let is_row_soft_wrapped = |row: usize| {
6701 row_infos
6702 .get(row)
6703 .map_or(true, |info| info.buffer_row.is_none())
6704 };
6705
6706 let start_anchor = if start_row == Default::default() {
6707 Anchor::min()
6708 } else {
6709 snapshot.buffer_snapshot.anchor_before(
6710 DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left),
6711 )
6712 };
6713 let end_anchor = if end_row > max_row {
6714 Anchor::max()
6715 } else {
6716 snapshot.buffer_snapshot.anchor_before(
6717 DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right),
6718 )
6719 };
6720
6721 let mut highlighted_rows = self
6722 .editor
6723 .update(cx, |editor, cx| editor.highlighted_display_rows(window, cx));
6724
6725 let is_light = cx.theme().appearance().is_light();
6726
6727 for (ix, row_info) in row_infos.iter().enumerate() {
6728 let Some(diff_status) = row_info.diff_status else {
6729 continue;
6730 };
6731
6732 let background_color = match diff_status.kind {
6733 DiffHunkStatusKind::Added => cx.theme().colors().version_control_added,
6734 DiffHunkStatusKind::Deleted => {
6735 cx.theme().colors().version_control_deleted
6736 }
6737 DiffHunkStatusKind::Modified => {
6738 debug_panic!("modified diff status for row info");
6739 continue;
6740 }
6741 };
6742
6743 let hunk_opacity = if is_light { 0.16 } else { 0.12 };
6744
6745 let hollow_highlight = LineHighlight {
6746 background: (background_color.opacity(if is_light {
6747 0.08
6748 } else {
6749 0.06
6750 }))
6751 .into(),
6752 border: Some(if is_light {
6753 background_color.opacity(0.48)
6754 } else {
6755 background_color.opacity(0.36)
6756 }),
6757 };
6758
6759 let filled_highlight =
6760 solid_background(background_color.opacity(hunk_opacity)).into();
6761
6762 let background = if Self::diff_hunk_hollow(diff_status, cx) {
6763 hollow_highlight
6764 } else {
6765 filled_highlight
6766 };
6767
6768 highlighted_rows
6769 .entry(start_row + DisplayRow(ix as u32))
6770 .or_insert(background);
6771 }
6772
6773 let highlighted_ranges = self.editor.read(cx).background_highlights_in_range(
6774 start_anchor..end_anchor,
6775 &snapshot.display_snapshot,
6776 cx.theme().colors(),
6777 );
6778 let highlighted_gutter_ranges =
6779 self.editor.read(cx).gutter_highlights_in_range(
6780 start_anchor..end_anchor,
6781 &snapshot.display_snapshot,
6782 cx,
6783 );
6784
6785 let redacted_ranges = self.editor.read(cx).redacted_ranges(
6786 start_anchor..end_anchor,
6787 &snapshot.display_snapshot,
6788 cx,
6789 );
6790
6791 let (local_selections, selected_buffer_ids): (
6792 Vec<Selection<Point>>,
6793 Vec<BufferId>,
6794 ) = self.editor.update(cx, |editor, cx| {
6795 let all_selections = editor.selections.all::<Point>(cx);
6796 let selected_buffer_ids = if editor.is_singleton(cx) {
6797 Vec::new()
6798 } else {
6799 let mut selected_buffer_ids = Vec::with_capacity(all_selections.len());
6800
6801 for selection in all_selections {
6802 for buffer_id in snapshot
6803 .buffer_snapshot
6804 .buffer_ids_for_range(selection.range())
6805 {
6806 if selected_buffer_ids.last() != Some(&buffer_id) {
6807 selected_buffer_ids.push(buffer_id);
6808 }
6809 }
6810 }
6811
6812 selected_buffer_ids
6813 };
6814
6815 let mut selections = editor
6816 .selections
6817 .disjoint_in_range(start_anchor..end_anchor, cx);
6818 selections.extend(editor.selections.pending(cx));
6819
6820 (selections, selected_buffer_ids)
6821 });
6822
6823 let (selections, mut active_rows, newest_selection_head) = self
6824 .layout_selections(
6825 start_anchor,
6826 end_anchor,
6827 &local_selections,
6828 &snapshot,
6829 start_row,
6830 end_row,
6831 window,
6832 cx,
6833 );
6834 let mut breakpoint_rows = self.editor.update(cx, |editor, cx| {
6835 editor.active_breakpoints(start_row..end_row, window, cx)
6836 });
6837 if cx.has_flag::<Debugger>() {
6838 for display_row in breakpoint_rows.keys() {
6839 active_rows.entry(*display_row).or_default().breakpoint = true;
6840 }
6841 }
6842
6843 let line_numbers = self.layout_line_numbers(
6844 Some(&gutter_hitbox),
6845 gutter_dimensions,
6846 line_height,
6847 scroll_position,
6848 start_row..end_row,
6849 &row_infos,
6850 &active_rows,
6851 newest_selection_head,
6852 &snapshot,
6853 window,
6854 cx,
6855 );
6856
6857 // We add the gutter breakpoint indicator to breakpoint_rows after painting
6858 // line numbers so we don't paint a line number debug accent color if a user
6859 // has their mouse over that line when a breakpoint isn't there
6860 if cx.has_flag::<Debugger>() {
6861 let gutter_breakpoint_indicator =
6862 self.editor.read(cx).gutter_breakpoint_indicator.0;
6863 if let Some((gutter_breakpoint_point, _)) =
6864 gutter_breakpoint_indicator.filter(|(_, is_active)| *is_active)
6865 {
6866 breakpoint_rows
6867 .entry(gutter_breakpoint_point.row())
6868 .or_insert_with(|| {
6869 let position = snapshot.display_point_to_anchor(
6870 gutter_breakpoint_point,
6871 Bias::Right,
6872 );
6873 let breakpoint = Breakpoint::new_standard();
6874
6875 (position, breakpoint)
6876 });
6877 }
6878 }
6879
6880 let mut expand_toggles =
6881 window.with_element_namespace("expand_toggles", |window| {
6882 self.layout_expand_toggles(
6883 &gutter_hitbox,
6884 gutter_dimensions,
6885 em_width,
6886 line_height,
6887 scroll_position,
6888 &row_infos,
6889 window,
6890 cx,
6891 )
6892 });
6893
6894 let mut crease_toggles =
6895 window.with_element_namespace("crease_toggles", |window| {
6896 self.layout_crease_toggles(
6897 start_row..end_row,
6898 &row_infos,
6899 &active_rows,
6900 &snapshot,
6901 window,
6902 cx,
6903 )
6904 });
6905 let crease_trailers =
6906 window.with_element_namespace("crease_trailers", |window| {
6907 self.layout_crease_trailers(
6908 row_infos.iter().copied(),
6909 &snapshot,
6910 window,
6911 cx,
6912 )
6913 });
6914
6915 let display_hunks = self.layout_gutter_diff_hunks(
6916 line_height,
6917 &gutter_hitbox,
6918 start_row..end_row,
6919 &snapshot,
6920 window,
6921 cx,
6922 );
6923
6924 let mut line_layouts = Self::layout_lines(
6925 start_row..end_row,
6926 &snapshot,
6927 &self.style,
6928 editor_width,
6929 is_row_soft_wrapped,
6930 window,
6931 cx,
6932 );
6933 let new_fold_widths = line_layouts
6934 .iter()
6935 .flat_map(|layout| &layout.fragments)
6936 .filter_map(|fragment| {
6937 if let LineFragment::Element { id, size, .. } = fragment {
6938 Some((*id, size.width))
6939 } else {
6940 None
6941 }
6942 });
6943 if self.editor.update(cx, |editor, cx| {
6944 editor.update_fold_widths(new_fold_widths, cx)
6945 }) {
6946 // If the fold widths have changed, we need to prepaint
6947 // the element again to account for any changes in
6948 // wrapping.
6949 return self.prepaint(None, bounds, &mut (), window, cx);
6950 }
6951
6952 let longest_line_blame_width = self
6953 .editor
6954 .update(cx, |editor, cx| {
6955 if !editor.show_git_blame_inline {
6956 return None;
6957 }
6958 let blame = editor.blame.as_ref()?;
6959 let blame_entry = blame
6960 .update(cx, |blame, cx| {
6961 let row_infos =
6962 snapshot.row_infos(snapshot.longest_row()).next()?;
6963 blame.blame_for_rows(&[row_infos], cx).next()
6964 })
6965 .flatten()?;
6966 let mut element = render_inline_blame_entry(
6967 self.editor.clone(),
6968 editor.workspace()?.downgrade(),
6969 blame,
6970 blame_entry,
6971 &style,
6972 cx,
6973 )?;
6974 let inline_blame_padding = INLINE_BLAME_PADDING_EM_WIDTHS * em_advance;
6975 Some(
6976 element
6977 .layout_as_root(AvailableSpace::min_size(), window, cx)
6978 .width
6979 + inline_blame_padding,
6980 )
6981 })
6982 .unwrap_or(Pixels::ZERO);
6983
6984 let longest_line_width = layout_line(
6985 snapshot.longest_row(),
6986 &snapshot,
6987 &style,
6988 editor_width,
6989 is_row_soft_wrapped,
6990 window,
6991 cx,
6992 )
6993 .width;
6994
6995 let scrollbar_layout_information = ScrollbarLayoutInformation::new(
6996 text_hitbox.bounds,
6997 glyph_grid_cell,
6998 size(longest_line_width, max_row.as_f32() * line_height),
6999 longest_line_blame_width,
7000 style.scrollbar_width,
7001 editor_width,
7002 EditorSettings::get_global(cx),
7003 );
7004
7005 let mut scroll_width = scrollbar_layout_information.scroll_range.width;
7006
7007 let sticky_header_excerpt = if snapshot.buffer_snapshot.show_headers() {
7008 snapshot.sticky_header_excerpt(scroll_position.y)
7009 } else {
7010 None
7011 };
7012 let sticky_header_excerpt_id =
7013 sticky_header_excerpt.as_ref().map(|top| top.excerpt.id);
7014
7015 let blocks = window.with_element_namespace("blocks", |window| {
7016 self.render_blocks(
7017 start_row..end_row,
7018 &snapshot,
7019 &hitbox,
7020 &text_hitbox,
7021 editor_width,
7022 &mut scroll_width,
7023 &gutter_dimensions,
7024 em_width,
7025 gutter_dimensions.full_width(),
7026 line_height,
7027 &mut line_layouts,
7028 &local_selections,
7029 &selected_buffer_ids,
7030 is_row_soft_wrapped,
7031 sticky_header_excerpt_id,
7032 window,
7033 cx,
7034 )
7035 });
7036 let (mut blocks, row_block_types) = match blocks {
7037 Ok(blocks) => blocks,
7038 Err(resized_blocks) => {
7039 self.editor.update(cx, |editor, cx| {
7040 editor.resize_blocks(resized_blocks, autoscroll_request, cx)
7041 });
7042 return self.prepaint(None, bounds, &mut (), window, cx);
7043 }
7044 };
7045
7046 let sticky_buffer_header = sticky_header_excerpt.map(|sticky_header_excerpt| {
7047 window.with_element_namespace("blocks", |window| {
7048 self.layout_sticky_buffer_header(
7049 sticky_header_excerpt,
7050 scroll_position.y,
7051 line_height,
7052 &snapshot,
7053 &hitbox,
7054 &selected_buffer_ids,
7055 &blocks,
7056 window,
7057 cx,
7058 )
7059 })
7060 });
7061
7062 let start_buffer_row =
7063 MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
7064 let end_buffer_row =
7065 MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
7066
7067 let scroll_max = point(
7068 ((scroll_width - editor_text_bounds.size.width) / em_width).max(0.0),
7069 max_scroll_top,
7070 );
7071
7072 self.editor.update(cx, |editor, cx| {
7073 let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
7074
7075 let autoscrolled = if autoscroll_horizontally {
7076 editor.autoscroll_horizontally(
7077 start_row,
7078 editor_width - (glyph_grid_cell.width / 2.0)
7079 + style.scrollbar_width,
7080 scroll_width,
7081 em_width,
7082 &line_layouts,
7083 cx,
7084 )
7085 } else {
7086 false
7087 };
7088
7089 if clamped || autoscrolled {
7090 snapshot = editor.snapshot(window, cx);
7091 scroll_position = snapshot.scroll_position();
7092 }
7093 });
7094
7095 let scroll_pixel_position = point(
7096 scroll_position.x * em_width,
7097 scroll_position.y * line_height,
7098 );
7099
7100 let indent_guides = self.layout_indent_guides(
7101 content_origin,
7102 text_hitbox.origin,
7103 start_buffer_row..end_buffer_row,
7104 scroll_pixel_position,
7105 line_height,
7106 &snapshot,
7107 window,
7108 cx,
7109 );
7110
7111 let crease_trailers =
7112 window.with_element_namespace("crease_trailers", |window| {
7113 self.prepaint_crease_trailers(
7114 crease_trailers,
7115 &line_layouts,
7116 line_height,
7117 content_origin,
7118 scroll_pixel_position,
7119 em_width,
7120 window,
7121 cx,
7122 )
7123 });
7124
7125 let (inline_completion_popover, inline_completion_popover_origin) = self
7126 .editor
7127 .update(cx, |editor, cx| {
7128 editor.render_edit_prediction_popover(
7129 &text_hitbox.bounds,
7130 content_origin,
7131 &snapshot,
7132 start_row..end_row,
7133 scroll_position.y,
7134 scroll_position.y + height_in_lines,
7135 &line_layouts,
7136 line_height,
7137 scroll_pixel_position,
7138 newest_selection_head,
7139 editor_width,
7140 &style,
7141 window,
7142 cx,
7143 )
7144 })
7145 .unzip();
7146
7147 let mut inline_diagnostics = self.layout_inline_diagnostics(
7148 &line_layouts,
7149 &crease_trailers,
7150 &row_block_types,
7151 content_origin,
7152 scroll_pixel_position,
7153 inline_completion_popover_origin,
7154 start_row,
7155 end_row,
7156 line_height,
7157 em_width,
7158 &style,
7159 window,
7160 cx,
7161 );
7162
7163 let mut inline_blame = None;
7164 if let Some(newest_selection_head) = newest_selection_head {
7165 let display_row = newest_selection_head.row();
7166 if (start_row..end_row).contains(&display_row)
7167 && !row_block_types.contains_key(&display_row)
7168 {
7169 let line_ix = display_row.minus(start_row) as usize;
7170 let row_info = &row_infos[line_ix];
7171 let line_layout = &line_layouts[line_ix];
7172 let crease_trailer_layout = crease_trailers[line_ix].as_ref();
7173 inline_blame = self.layout_inline_blame(
7174 display_row,
7175 row_info,
7176 line_layout,
7177 crease_trailer_layout,
7178 em_width,
7179 content_origin,
7180 scroll_pixel_position,
7181 line_height,
7182 window,
7183 cx,
7184 );
7185 if inline_blame.is_some() {
7186 // Blame overrides inline diagnostics
7187 inline_diagnostics.remove(&display_row);
7188 }
7189 }
7190 }
7191
7192 let blamed_display_rows = self.layout_blame_entries(
7193 &row_infos,
7194 em_width,
7195 scroll_position,
7196 line_height,
7197 &gutter_hitbox,
7198 gutter_dimensions.git_blame_entries_width,
7199 window,
7200 cx,
7201 );
7202
7203 self.editor.update(cx, |editor, cx| {
7204 let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
7205
7206 let autoscrolled = if autoscroll_horizontally {
7207 editor.autoscroll_horizontally(
7208 start_row,
7209 editor_width - (glyph_grid_cell.width / 2.0)
7210 + style.scrollbar_width,
7211 scroll_width,
7212 em_width,
7213 &line_layouts,
7214 cx,
7215 )
7216 } else {
7217 false
7218 };
7219
7220 if clamped || autoscrolled {
7221 snapshot = editor.snapshot(window, cx);
7222 scroll_position = snapshot.scroll_position();
7223 }
7224 });
7225
7226 let line_elements = self.prepaint_lines(
7227 start_row,
7228 &mut line_layouts,
7229 line_height,
7230 scroll_pixel_position,
7231 content_origin,
7232 window,
7233 cx,
7234 );
7235
7236 window.with_element_namespace("blocks", |window| {
7237 self.layout_blocks(
7238 &mut blocks,
7239 &hitbox,
7240 line_height,
7241 scroll_pixel_position,
7242 window,
7243 cx,
7244 );
7245 });
7246
7247 let cursors = self.collect_cursors(&snapshot, cx);
7248 let visible_row_range = start_row..end_row;
7249 let non_visible_cursors = cursors
7250 .iter()
7251 .any(|c| !visible_row_range.contains(&c.0.row()));
7252
7253 let visible_cursors = self.layout_visible_cursors(
7254 &snapshot,
7255 &selections,
7256 &row_block_types,
7257 start_row..end_row,
7258 &line_layouts,
7259 &text_hitbox,
7260 content_origin,
7261 scroll_position,
7262 scroll_pixel_position,
7263 line_height,
7264 em_width,
7265 em_advance,
7266 autoscroll_containing_element,
7267 window,
7268 cx,
7269 );
7270
7271 let scrollbars_layout = self.layout_scrollbars(
7272 &snapshot,
7273 scrollbar_layout_information,
7274 content_offset,
7275 scroll_position,
7276 non_visible_cursors,
7277 window,
7278 cx,
7279 );
7280
7281 let gutter_settings = EditorSettings::get_global(cx).gutter;
7282
7283 let mut code_actions_indicator = None;
7284 if let Some(newest_selection_head) = newest_selection_head {
7285 let newest_selection_point =
7286 newest_selection_head.to_point(&snapshot.display_snapshot);
7287
7288 if (start_row..end_row).contains(&newest_selection_head.row()) {
7289 self.layout_cursor_popovers(
7290 line_height,
7291 &text_hitbox,
7292 content_origin,
7293 start_row,
7294 scroll_pixel_position,
7295 &line_layouts,
7296 newest_selection_head,
7297 newest_selection_point,
7298 &style,
7299 window,
7300 cx,
7301 );
7302
7303 let show_code_actions = snapshot
7304 .show_code_actions
7305 .unwrap_or(gutter_settings.code_actions);
7306 if show_code_actions {
7307 let newest_selection_point =
7308 newest_selection_head.to_point(&snapshot.display_snapshot);
7309 if !snapshot
7310 .is_line_folded(MultiBufferRow(newest_selection_point.row))
7311 {
7312 let buffer = snapshot.buffer_snapshot.buffer_line_for_row(
7313 MultiBufferRow(newest_selection_point.row),
7314 );
7315 if let Some((buffer, range)) = buffer {
7316 let buffer_id = buffer.remote_id();
7317 let row = range.start.row;
7318 let has_test_indicator = self
7319 .editor
7320 .read(cx)
7321 .tasks
7322 .contains_key(&(buffer_id, row));
7323
7324 let has_expand_indicator = row_infos
7325 .get(
7326 (newest_selection_head.row() - start_row).0
7327 as usize,
7328 )
7329 .is_some_and(|row_info| row_info.expand_info.is_some());
7330
7331 if !has_test_indicator && !has_expand_indicator {
7332 code_actions_indicator = self
7333 .layout_code_actions_indicator(
7334 line_height,
7335 newest_selection_head,
7336 scroll_pixel_position,
7337 &gutter_dimensions,
7338 &gutter_hitbox,
7339 &mut breakpoint_rows,
7340 &display_hunks,
7341 window,
7342 cx,
7343 );
7344 }
7345 }
7346 }
7347 }
7348 }
7349 }
7350
7351 self.layout_gutter_menu(
7352 line_height,
7353 &text_hitbox,
7354 content_origin,
7355 scroll_pixel_position,
7356 gutter_dimensions.width - gutter_dimensions.left_padding,
7357 window,
7358 cx,
7359 );
7360
7361 let test_indicators = if gutter_settings.runnables {
7362 self.layout_run_indicators(
7363 line_height,
7364 start_row..end_row,
7365 &row_infos,
7366 scroll_pixel_position,
7367 &gutter_dimensions,
7368 &gutter_hitbox,
7369 &display_hunks,
7370 &snapshot,
7371 &mut breakpoint_rows,
7372 window,
7373 cx,
7374 )
7375 } else {
7376 Vec::new()
7377 };
7378
7379 let show_breakpoints = snapshot
7380 .show_breakpoints
7381 .unwrap_or(gutter_settings.breakpoints);
7382 let breakpoints = if cx.has_flag::<Debugger>() && show_breakpoints {
7383 self.layout_breakpoints(
7384 line_height,
7385 start_row..end_row,
7386 scroll_pixel_position,
7387 &gutter_dimensions,
7388 &gutter_hitbox,
7389 &display_hunks,
7390 &snapshot,
7391 breakpoint_rows,
7392 &row_infos,
7393 window,
7394 cx,
7395 )
7396 } else {
7397 vec![]
7398 };
7399
7400 self.layout_signature_help(
7401 &hitbox,
7402 content_origin,
7403 scroll_pixel_position,
7404 newest_selection_head,
7405 start_row,
7406 &line_layouts,
7407 line_height,
7408 em_width,
7409 window,
7410 cx,
7411 );
7412
7413 if !cx.has_active_drag() {
7414 self.layout_hover_popovers(
7415 &snapshot,
7416 &hitbox,
7417 &text_hitbox,
7418 start_row..end_row,
7419 content_origin,
7420 scroll_pixel_position,
7421 &line_layouts,
7422 line_height,
7423 em_width,
7424 window,
7425 cx,
7426 );
7427 }
7428
7429 let mouse_context_menu = self.layout_mouse_context_menu(
7430 &snapshot,
7431 start_row..end_row,
7432 content_origin,
7433 window,
7434 cx,
7435 );
7436
7437 window.with_element_namespace("crease_toggles", |window| {
7438 self.prepaint_crease_toggles(
7439 &mut crease_toggles,
7440 line_height,
7441 &gutter_dimensions,
7442 gutter_settings,
7443 scroll_pixel_position,
7444 &gutter_hitbox,
7445 window,
7446 cx,
7447 )
7448 });
7449
7450 window.with_element_namespace("expand_toggles", |window| {
7451 self.prepaint_expand_toggles(&mut expand_toggles, window, cx)
7452 });
7453
7454 let invisible_symbol_font_size = font_size / 2.;
7455 let tab_invisible = window
7456 .text_system()
7457 .shape_line(
7458 "→".into(),
7459 invisible_symbol_font_size,
7460 &[TextRun {
7461 len: "→".len(),
7462 font: self.style.text.font(),
7463 color: cx.theme().colors().editor_invisible,
7464 background_color: None,
7465 underline: None,
7466 strikethrough: None,
7467 }],
7468 )
7469 .unwrap();
7470 let space_invisible = window
7471 .text_system()
7472 .shape_line(
7473 "•".into(),
7474 invisible_symbol_font_size,
7475 &[TextRun {
7476 len: "•".len(),
7477 font: self.style.text.font(),
7478 color: cx.theme().colors().editor_invisible,
7479 background_color: None,
7480 underline: None,
7481 strikethrough: None,
7482 }],
7483 )
7484 .unwrap();
7485
7486 let mode = snapshot.mode;
7487
7488 let position_map = Rc::new(PositionMap {
7489 size: bounds.size,
7490 visible_row_range,
7491 scroll_pixel_position,
7492 scroll_max,
7493 line_layouts,
7494 line_height,
7495 em_width,
7496 em_advance,
7497 snapshot,
7498 gutter_hitbox: gutter_hitbox.clone(),
7499 text_hitbox: text_hitbox.clone(),
7500 });
7501
7502 self.editor.update(cx, |editor, _| {
7503 editor.last_position_map = Some(position_map.clone())
7504 });
7505
7506 let diff_hunk_controls = if is_read_only {
7507 vec![]
7508 } else {
7509 self.layout_diff_hunk_controls(
7510 start_row..end_row,
7511 &row_infos,
7512 &text_hitbox,
7513 &position_map,
7514 newest_selection_head,
7515 line_height,
7516 scroll_pixel_position,
7517 &display_hunks,
7518 self.editor.clone(),
7519 window,
7520 cx,
7521 )
7522 };
7523
7524 EditorLayout {
7525 mode,
7526 position_map,
7527 visible_display_row_range: start_row..end_row,
7528 wrap_guides,
7529 indent_guides,
7530 hitbox,
7531 gutter_hitbox,
7532 display_hunks,
7533 content_origin,
7534 scrollbars_layout,
7535 active_rows,
7536 highlighted_rows,
7537 highlighted_ranges,
7538 highlighted_gutter_ranges,
7539 redacted_ranges,
7540 line_elements,
7541 line_numbers,
7542 blamed_display_rows,
7543 inline_diagnostics,
7544 inline_blame,
7545 blocks,
7546 cursors,
7547 visible_cursors,
7548 selections,
7549 inline_completion_popover,
7550 diff_hunk_controls,
7551 mouse_context_menu,
7552 test_indicators,
7553 breakpoints,
7554 code_actions_indicator,
7555 crease_toggles,
7556 crease_trailers,
7557 tab_invisible,
7558 space_invisible,
7559 sticky_buffer_header,
7560 expand_toggles,
7561 }
7562 })
7563 })
7564 })
7565 }
7566
7567 fn paint(
7568 &mut self,
7569 _: Option<&GlobalElementId>,
7570 bounds: Bounds<gpui::Pixels>,
7571 _: &mut Self::RequestLayoutState,
7572 layout: &mut Self::PrepaintState,
7573 window: &mut Window,
7574 cx: &mut App,
7575 ) {
7576 let focus_handle = self.editor.focus_handle(cx);
7577 let key_context = self
7578 .editor
7579 .update(cx, |editor, cx| editor.key_context(window, cx));
7580
7581 window.set_key_context(key_context);
7582 window.handle_input(
7583 &focus_handle,
7584 ElementInputHandler::new(bounds, self.editor.clone()),
7585 cx,
7586 );
7587 self.register_actions(window, cx);
7588 self.register_key_listeners(window, cx, layout);
7589
7590 let text_style = TextStyleRefinement {
7591 font_size: Some(self.style.text.font_size),
7592 line_height: Some(self.style.text.line_height),
7593 ..Default::default()
7594 };
7595 let rem_size = self.rem_size(cx);
7596 window.with_rem_size(rem_size, |window| {
7597 window.with_text_style(Some(text_style), |window| {
7598 window.with_content_mask(Some(ContentMask { bounds }), |window| {
7599 self.paint_mouse_listeners(layout, window, cx);
7600 self.paint_background(layout, window, cx);
7601 self.paint_indent_guides(layout, window, cx);
7602
7603 if layout.gutter_hitbox.size.width > Pixels::ZERO {
7604 self.paint_blamed_display_rows(layout, window, cx);
7605 self.paint_line_numbers(layout, window, cx);
7606 }
7607
7608 self.paint_text(layout, window, cx);
7609
7610 if layout.gutter_hitbox.size.width > Pixels::ZERO {
7611 self.paint_gutter_highlights(layout, window, cx);
7612 self.paint_gutter_indicators(layout, window, cx);
7613 }
7614
7615 if !layout.blocks.is_empty() {
7616 window.with_element_namespace("blocks", |window| {
7617 self.paint_blocks(layout, window, cx);
7618 });
7619 }
7620
7621 window.with_element_namespace("blocks", |window| {
7622 if let Some(mut sticky_header) = layout.sticky_buffer_header.take() {
7623 sticky_header.paint(window, cx)
7624 }
7625 });
7626
7627 self.paint_scrollbars(layout, window, cx);
7628 self.paint_inline_completion_popover(layout, window, cx);
7629 self.paint_mouse_context_menu(layout, window, cx);
7630 });
7631 })
7632 })
7633 }
7634}
7635
7636pub(super) fn gutter_bounds(
7637 editor_bounds: Bounds<Pixels>,
7638 gutter_dimensions: GutterDimensions,
7639) -> Bounds<Pixels> {
7640 Bounds {
7641 origin: editor_bounds.origin,
7642 size: size(gutter_dimensions.width, editor_bounds.size.height),
7643 }
7644}
7645
7646/// Holds information required for layouting the editor scrollbars.
7647struct ScrollbarLayoutInformation {
7648 /// The bounds of the editor area (excluding the content offset).
7649 editor_bounds: Bounds<Pixels>,
7650 /// The available range to scroll within the document.
7651 scroll_range: Size<Pixels>,
7652 /// The space available for one glyph in the editor.
7653 glyph_grid_cell: Size<Pixels>,
7654}
7655
7656impl ScrollbarLayoutInformation {
7657 pub fn new(
7658 editor_bounds: Bounds<Pixels>,
7659 glyph_grid_cell: Size<Pixels>,
7660 document_size: Size<Pixels>,
7661 longest_line_blame_width: Pixels,
7662 scrollbar_width: Pixels,
7663 editor_width: Pixels,
7664 settings: &EditorSettings,
7665 ) -> Self {
7666 let vertical_overscroll = match settings.scroll_beyond_last_line {
7667 ScrollBeyondLastLine::OnePage => editor_bounds.size.height,
7668 ScrollBeyondLastLine::Off => glyph_grid_cell.height,
7669 ScrollBeyondLastLine::VerticalScrollMargin => {
7670 (1.0 + settings.vertical_scroll_margin) * glyph_grid_cell.height
7671 }
7672 };
7673
7674 let right_margin = if document_size.width + longest_line_blame_width >= editor_width {
7675 glyph_grid_cell.width + scrollbar_width
7676 } else {
7677 px(0.0)
7678 };
7679
7680 let overscroll = size(right_margin + longest_line_blame_width, vertical_overscroll);
7681
7682 let scroll_range = document_size + overscroll;
7683
7684 ScrollbarLayoutInformation {
7685 editor_bounds,
7686 scroll_range,
7687 glyph_grid_cell,
7688 }
7689 }
7690}
7691
7692impl IntoElement for EditorElement {
7693 type Element = Self;
7694
7695 fn into_element(self) -> Self::Element {
7696 self
7697 }
7698}
7699
7700pub struct EditorLayout {
7701 position_map: Rc<PositionMap>,
7702 hitbox: Hitbox,
7703 gutter_hitbox: Hitbox,
7704 content_origin: gpui::Point<Pixels>,
7705 scrollbars_layout: Option<EditorScrollbars>,
7706 mode: EditorMode,
7707 wrap_guides: SmallVec<[(Pixels, bool); 2]>,
7708 indent_guides: Option<Vec<IndentGuideLayout>>,
7709 visible_display_row_range: Range<DisplayRow>,
7710 active_rows: BTreeMap<DisplayRow, LineHighlightSpec>,
7711 highlighted_rows: BTreeMap<DisplayRow, LineHighlight>,
7712 line_elements: SmallVec<[AnyElement; 1]>,
7713 line_numbers: Arc<HashMap<MultiBufferRow, LineNumberLayout>>,
7714 display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
7715 blamed_display_rows: Option<Vec<AnyElement>>,
7716 inline_diagnostics: HashMap<DisplayRow, AnyElement>,
7717 inline_blame: Option<AnyElement>,
7718 blocks: Vec<BlockLayout>,
7719 highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
7720 highlighted_gutter_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
7721 redacted_ranges: Vec<Range<DisplayPoint>>,
7722 cursors: Vec<(DisplayPoint, Hsla)>,
7723 visible_cursors: Vec<CursorLayout>,
7724 selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
7725 code_actions_indicator: Option<AnyElement>,
7726 test_indicators: Vec<AnyElement>,
7727 breakpoints: Vec<AnyElement>,
7728 crease_toggles: Vec<Option<AnyElement>>,
7729 expand_toggles: Vec<Option<(AnyElement, gpui::Point<Pixels>)>>,
7730 diff_hunk_controls: Vec<AnyElement>,
7731 crease_trailers: Vec<Option<CreaseTrailerLayout>>,
7732 inline_completion_popover: Option<AnyElement>,
7733 mouse_context_menu: Option<AnyElement>,
7734 tab_invisible: ShapedLine,
7735 space_invisible: ShapedLine,
7736 sticky_buffer_header: Option<AnyElement>,
7737}
7738
7739impl EditorLayout {
7740 fn line_end_overshoot(&self) -> Pixels {
7741 0.15 * self.position_map.line_height
7742 }
7743}
7744
7745struct LineNumberLayout {
7746 shaped_line: ShapedLine,
7747 hitbox: Option<Hitbox>,
7748}
7749
7750struct ColoredRange<T> {
7751 start: T,
7752 end: T,
7753 color: Hsla,
7754}
7755
7756impl Along for ScrollbarAxes {
7757 type Unit = bool;
7758
7759 fn along(&self, axis: ScrollbarAxis) -> Self::Unit {
7760 match axis {
7761 ScrollbarAxis::Horizontal => self.horizontal,
7762 ScrollbarAxis::Vertical => self.vertical,
7763 }
7764 }
7765
7766 fn apply_along(&self, axis: ScrollbarAxis, f: impl FnOnce(Self::Unit) -> Self::Unit) -> Self {
7767 match axis {
7768 ScrollbarAxis::Horizontal => ScrollbarAxes {
7769 horizontal: f(self.horizontal),
7770 vertical: self.vertical,
7771 },
7772 ScrollbarAxis::Vertical => ScrollbarAxes {
7773 horizontal: self.horizontal,
7774 vertical: f(self.vertical),
7775 },
7776 }
7777 }
7778}
7779
7780#[derive(Clone)]
7781struct EditorScrollbars {
7782 pub vertical: Option<ScrollbarLayout>,
7783 pub horizontal: Option<ScrollbarLayout>,
7784 pub visible: bool,
7785}
7786
7787impl EditorScrollbars {
7788 pub fn from_scrollbar_axes(
7789 settings_visibility: ScrollbarAxes,
7790 layout_information: &ScrollbarLayoutInformation,
7791 content_offset: gpui::Point<Pixels>,
7792 scroll_position: gpui::Point<f32>,
7793 scrollbar_width: Pixels,
7794 show_scrollbars: bool,
7795 window: &mut Window,
7796 ) -> Self {
7797 let ScrollbarLayoutInformation {
7798 editor_bounds,
7799 scroll_range,
7800 glyph_grid_cell,
7801 } = layout_information;
7802
7803 let scrollbar_bounds_for = |axis: ScrollbarAxis| match axis {
7804 ScrollbarAxis::Horizontal => Bounds::from_corner_and_size(
7805 Corner::BottomLeft,
7806 editor_bounds.bottom_left(),
7807 size(
7808 if settings_visibility.vertical {
7809 editor_bounds.size.width - scrollbar_width
7810 } else {
7811 editor_bounds.size.width
7812 },
7813 scrollbar_width,
7814 ),
7815 ),
7816 ScrollbarAxis::Vertical => Bounds::from_corner_and_size(
7817 Corner::TopRight,
7818 editor_bounds.top_right(),
7819 size(scrollbar_width, editor_bounds.size.height),
7820 ),
7821 };
7822
7823 let mut create_scrollbar_layout = |axis| {
7824 settings_visibility
7825 .along(axis)
7826 .then(|| {
7827 (
7828 editor_bounds.size.along(axis) - content_offset.along(axis),
7829 scroll_range.along(axis),
7830 )
7831 })
7832 .filter(|(editor_content_size, scroll_range)| {
7833 // The scrollbar should only be rendered if the content does
7834 // not entirely fit into the editor
7835 // However, this only applies to the horizontal scrollbar, as information about the
7836 // vertical scrollbar layout is always needed for scrollbar diagnostics.
7837 axis != ScrollbarAxis::Horizontal || editor_content_size < scroll_range
7838 })
7839 .map(|(editor_content_size, scroll_range)| {
7840 ScrollbarLayout::new(
7841 window.insert_hitbox(scrollbar_bounds_for(axis), false),
7842 editor_content_size,
7843 scroll_range,
7844 glyph_grid_cell.along(axis),
7845 content_offset.along(axis),
7846 scroll_position.along(axis),
7847 axis,
7848 )
7849 })
7850 };
7851
7852 Self {
7853 vertical: create_scrollbar_layout(ScrollbarAxis::Vertical),
7854 horizontal: create_scrollbar_layout(ScrollbarAxis::Horizontal),
7855 visible: show_scrollbars,
7856 }
7857 }
7858
7859 pub fn iter_scrollbars(&self) -> impl Iterator<Item = (&ScrollbarLayout, ScrollbarAxis)> + '_ {
7860 [
7861 (&self.vertical, ScrollbarAxis::Vertical),
7862 (&self.horizontal, ScrollbarAxis::Horizontal),
7863 ]
7864 .into_iter()
7865 .filter_map(|(scrollbar, axis)| scrollbar.as_ref().map(|s| (s, axis)))
7866 }
7867
7868 /// Returns the currently hovered scrollbar axis, if any.
7869 pub fn get_hovered_axis(&self, window: &Window) -> Option<(&ScrollbarLayout, ScrollbarAxis)> {
7870 self.iter_scrollbars()
7871 .find(|s| s.0.hitbox.is_hovered(window))
7872 }
7873}
7874
7875#[derive(Clone)]
7876struct ScrollbarLayout {
7877 hitbox: Hitbox,
7878 visible_range: Range<f32>,
7879 text_unit_size: Pixels,
7880 content_offset: Pixels,
7881 thumb_size: Pixels,
7882 axis: ScrollbarAxis,
7883}
7884
7885impl ScrollbarLayout {
7886 const BORDER_WIDTH: Pixels = px(1.0);
7887 const LINE_MARKER_HEIGHT: Pixels = px(2.0);
7888 const MIN_MARKER_HEIGHT: Pixels = px(5.0);
7889 const MIN_THUMB_SIZE: Pixels = px(25.0);
7890
7891 fn new(
7892 scrollbar_track_hitbox: Hitbox,
7893 editor_content_size: Pixels,
7894 scroll_range: Pixels,
7895 glyph_space: Pixels,
7896 content_offset: Pixels,
7897 scroll_position: f32,
7898 axis: ScrollbarAxis,
7899 ) -> Self {
7900 let track_bounds = scrollbar_track_hitbox.bounds;
7901 // The length of the track available to the scrollbar thumb. We deliberately
7902 // exclude the content size here so that the thumb aligns with the content.
7903 let track_length = track_bounds.size.along(axis) - content_offset;
7904
7905 let text_units_per_page = editor_content_size / glyph_space;
7906 let visible_range = scroll_position..scroll_position + text_units_per_page;
7907 let total_text_units = scroll_range / glyph_space;
7908
7909 let thumb_percentage = text_units_per_page / total_text_units;
7910 let thumb_size = (track_length * thumb_percentage)
7911 .max(ScrollbarLayout::MIN_THUMB_SIZE)
7912 .min(track_length);
7913 let text_unit_size =
7914 (track_length - thumb_size) / (total_text_units - text_units_per_page).max(0.);
7915
7916 ScrollbarLayout {
7917 hitbox: scrollbar_track_hitbox,
7918 visible_range,
7919 text_unit_size,
7920 content_offset,
7921 thumb_size,
7922 axis,
7923 }
7924 }
7925
7926 fn thumb_bounds(&self) -> Bounds<Pixels> {
7927 let scrollbar_track = &self.hitbox.bounds;
7928 Bounds::new(
7929 scrollbar_track
7930 .origin
7931 .apply_along(self.axis, |origin| self.thumb_origin(origin)),
7932 scrollbar_track
7933 .size
7934 .apply_along(self.axis, |_| self.thumb_size),
7935 )
7936 }
7937
7938 fn thumb_origin(&self, origin: Pixels) -> Pixels {
7939 origin + self.content_offset + self.visible_range.start * self.text_unit_size
7940 }
7941
7942 fn marker_quads_for_ranges(
7943 &self,
7944 row_ranges: impl IntoIterator<Item = ColoredRange<DisplayRow>>,
7945 column: Option<usize>,
7946 ) -> Vec<PaintQuad> {
7947 struct MinMax {
7948 min: Pixels,
7949 max: Pixels,
7950 }
7951 let (x_range, height_limit) = if let Some(column) = column {
7952 let column_width = px(((self.hitbox.size.width - Self::BORDER_WIDTH).0 / 3.0).floor());
7953 let start = Self::BORDER_WIDTH + (column as f32 * column_width);
7954 let end = start + column_width;
7955 (
7956 Range { start, end },
7957 MinMax {
7958 min: Self::MIN_MARKER_HEIGHT,
7959 max: px(f32::MAX),
7960 },
7961 )
7962 } else {
7963 (
7964 Range {
7965 start: Self::BORDER_WIDTH,
7966 end: self.hitbox.size.width,
7967 },
7968 MinMax {
7969 min: Self::LINE_MARKER_HEIGHT,
7970 max: Self::LINE_MARKER_HEIGHT,
7971 },
7972 )
7973 };
7974
7975 let row_to_y = |row: DisplayRow| row.as_f32() * self.text_unit_size;
7976 let mut pixel_ranges = row_ranges
7977 .into_iter()
7978 .map(|range| {
7979 let start_y = row_to_y(range.start);
7980 let end_y = row_to_y(range.end)
7981 + self
7982 .text_unit_size
7983 .max(height_limit.min)
7984 .min(height_limit.max);
7985 ColoredRange {
7986 start: start_y,
7987 end: end_y,
7988 color: range.color,
7989 }
7990 })
7991 .peekable();
7992
7993 let mut quads = Vec::new();
7994 while let Some(mut pixel_range) = pixel_ranges.next() {
7995 while let Some(next_pixel_range) = pixel_ranges.peek() {
7996 if pixel_range.end >= next_pixel_range.start - px(1.0)
7997 && pixel_range.color == next_pixel_range.color
7998 {
7999 pixel_range.end = next_pixel_range.end.max(pixel_range.end);
8000 pixel_ranges.next();
8001 } else {
8002 break;
8003 }
8004 }
8005
8006 let bounds = Bounds::from_corners(
8007 point(x_range.start, pixel_range.start),
8008 point(x_range.end, pixel_range.end),
8009 );
8010 quads.push(quad(
8011 bounds,
8012 Corners::default(),
8013 pixel_range.color,
8014 Edges::default(),
8015 Hsla::transparent_black(),
8016 BorderStyle::default(),
8017 ));
8018 }
8019
8020 quads
8021 }
8022}
8023
8024struct CreaseTrailerLayout {
8025 element: AnyElement,
8026 bounds: Bounds<Pixels>,
8027}
8028
8029pub(crate) struct PositionMap {
8030 pub size: Size<Pixels>,
8031 pub line_height: Pixels,
8032 pub scroll_pixel_position: gpui::Point<Pixels>,
8033 pub scroll_max: gpui::Point<f32>,
8034 pub em_width: Pixels,
8035 pub em_advance: Pixels,
8036 pub visible_row_range: Range<DisplayRow>,
8037 pub line_layouts: Vec<LineWithInvisibles>,
8038 pub snapshot: EditorSnapshot,
8039 pub text_hitbox: Hitbox,
8040 pub gutter_hitbox: Hitbox,
8041}
8042
8043#[derive(Debug, Copy, Clone)]
8044pub struct PointForPosition {
8045 pub previous_valid: DisplayPoint,
8046 pub next_valid: DisplayPoint,
8047 pub exact_unclipped: DisplayPoint,
8048 pub column_overshoot_after_line_end: u32,
8049}
8050
8051impl PointForPosition {
8052 pub fn as_valid(&self) -> Option<DisplayPoint> {
8053 if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped {
8054 Some(self.previous_valid)
8055 } else {
8056 None
8057 }
8058 }
8059}
8060
8061impl PositionMap {
8062 pub(crate) fn point_for_position(&self, position: gpui::Point<Pixels>) -> PointForPosition {
8063 let text_bounds = self.text_hitbox.bounds;
8064 let scroll_position = self.snapshot.scroll_position();
8065 let position = position - text_bounds.origin;
8066 let y = position.y.max(px(0.)).min(self.size.height);
8067 let x = position.x + (scroll_position.x * self.em_width);
8068 let row = ((y / self.line_height) + scroll_position.y) as u32;
8069
8070 let (column, x_overshoot_after_line_end) = if let Some(line) = self
8071 .line_layouts
8072 .get(row as usize - scroll_position.y as usize)
8073 {
8074 if let Some(ix) = line.index_for_x(x) {
8075 (ix as u32, px(0.))
8076 } else {
8077 (line.len as u32, px(0.).max(x - line.width))
8078 }
8079 } else {
8080 (0, x)
8081 };
8082
8083 let mut exact_unclipped = DisplayPoint::new(DisplayRow(row), column);
8084 let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
8085 let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
8086
8087 let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32;
8088 *exact_unclipped.column_mut() += column_overshoot_after_line_end;
8089 PointForPosition {
8090 previous_valid,
8091 next_valid,
8092 exact_unclipped,
8093 column_overshoot_after_line_end,
8094 }
8095 }
8096}
8097
8098struct BlockLayout {
8099 id: BlockId,
8100 x_offset: Pixels,
8101 row: Option<DisplayRow>,
8102 element: AnyElement,
8103 available_space: Size<AvailableSpace>,
8104 style: BlockStyle,
8105 overlaps_gutter: bool,
8106 is_buffer_header: bool,
8107}
8108
8109pub fn layout_line(
8110 row: DisplayRow,
8111 snapshot: &EditorSnapshot,
8112 style: &EditorStyle,
8113 text_width: Pixels,
8114 is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
8115 window: &mut Window,
8116 cx: &mut App,
8117) -> LineWithInvisibles {
8118 let chunks = snapshot.highlighted_chunks(row..row + DisplayRow(1), true, style);
8119 LineWithInvisibles::from_chunks(
8120 chunks,
8121 &style,
8122 MAX_LINE_LEN,
8123 1,
8124 snapshot.mode,
8125 text_width,
8126 is_row_soft_wrapped,
8127 window,
8128 cx,
8129 )
8130 .pop()
8131 .unwrap()
8132}
8133
8134#[derive(Debug)]
8135pub struct IndentGuideLayout {
8136 origin: gpui::Point<Pixels>,
8137 length: Pixels,
8138 single_indent_width: Pixels,
8139 depth: u32,
8140 active: bool,
8141 settings: IndentGuideSettings,
8142}
8143
8144pub struct CursorLayout {
8145 origin: gpui::Point<Pixels>,
8146 block_width: Pixels,
8147 line_height: Pixels,
8148 color: Hsla,
8149 shape: CursorShape,
8150 block_text: Option<ShapedLine>,
8151 cursor_name: Option<AnyElement>,
8152}
8153
8154#[derive(Debug)]
8155pub struct CursorName {
8156 string: SharedString,
8157 color: Hsla,
8158 is_top_row: bool,
8159}
8160
8161impl CursorLayout {
8162 pub fn new(
8163 origin: gpui::Point<Pixels>,
8164 block_width: Pixels,
8165 line_height: Pixels,
8166 color: Hsla,
8167 shape: CursorShape,
8168 block_text: Option<ShapedLine>,
8169 ) -> CursorLayout {
8170 CursorLayout {
8171 origin,
8172 block_width,
8173 line_height,
8174 color,
8175 shape,
8176 block_text,
8177 cursor_name: None,
8178 }
8179 }
8180
8181 pub fn bounding_rect(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
8182 Bounds {
8183 origin: self.origin + origin,
8184 size: size(self.block_width, self.line_height),
8185 }
8186 }
8187
8188 fn bounds(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
8189 match self.shape {
8190 CursorShape::Bar => Bounds {
8191 origin: self.origin + origin,
8192 size: size(px(2.0), self.line_height),
8193 },
8194 CursorShape::Block | CursorShape::Hollow => Bounds {
8195 origin: self.origin + origin,
8196 size: size(self.block_width, self.line_height),
8197 },
8198 CursorShape::Underline => Bounds {
8199 origin: self.origin
8200 + origin
8201 + gpui::Point::new(Pixels::ZERO, self.line_height - px(2.0)),
8202 size: size(self.block_width, px(2.0)),
8203 },
8204 }
8205 }
8206
8207 pub fn layout(
8208 &mut self,
8209 origin: gpui::Point<Pixels>,
8210 cursor_name: Option<CursorName>,
8211 window: &mut Window,
8212 cx: &mut App,
8213 ) {
8214 if let Some(cursor_name) = cursor_name {
8215 let bounds = self.bounds(origin);
8216 let text_size = self.line_height / 1.5;
8217
8218 let name_origin = if cursor_name.is_top_row {
8219 point(bounds.right() - px(1.), bounds.top())
8220 } else {
8221 match self.shape {
8222 CursorShape::Bar => point(
8223 bounds.right() - px(2.),
8224 bounds.top() - text_size / 2. - px(1.),
8225 ),
8226 _ => point(
8227 bounds.right() - px(1.),
8228 bounds.top() - text_size / 2. - px(1.),
8229 ),
8230 }
8231 };
8232 let mut name_element = div()
8233 .bg(self.color)
8234 .text_size(text_size)
8235 .px_0p5()
8236 .line_height(text_size + px(2.))
8237 .text_color(cursor_name.color)
8238 .child(cursor_name.string.clone())
8239 .into_any_element();
8240
8241 name_element.prepaint_as_root(name_origin, AvailableSpace::min_size(), window, cx);
8242
8243 self.cursor_name = Some(name_element);
8244 }
8245 }
8246
8247 pub fn paint(&mut self, origin: gpui::Point<Pixels>, window: &mut Window, cx: &mut App) {
8248 let bounds = self.bounds(origin);
8249
8250 //Draw background or border quad
8251 let cursor = if matches!(self.shape, CursorShape::Hollow) {
8252 outline(bounds, self.color, BorderStyle::Solid)
8253 } else {
8254 fill(bounds, self.color)
8255 };
8256
8257 if let Some(name) = &mut self.cursor_name {
8258 name.paint(window, cx);
8259 }
8260
8261 window.paint_quad(cursor);
8262
8263 if let Some(block_text) = &self.block_text {
8264 block_text
8265 .paint(self.origin + origin, self.line_height, window, cx)
8266 .log_err();
8267 }
8268 }
8269
8270 pub fn shape(&self) -> CursorShape {
8271 self.shape
8272 }
8273}
8274
8275#[derive(Debug)]
8276pub struct HighlightedRange {
8277 pub start_y: Pixels,
8278 pub line_height: Pixels,
8279 pub lines: Vec<HighlightedRangeLine>,
8280 pub color: Hsla,
8281 pub corner_radius: Pixels,
8282}
8283
8284#[derive(Debug)]
8285pub struct HighlightedRangeLine {
8286 pub start_x: Pixels,
8287 pub end_x: Pixels,
8288}
8289
8290impl HighlightedRange {
8291 pub fn paint(&self, bounds: Bounds<Pixels>, window: &mut Window) {
8292 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
8293 self.paint_lines(self.start_y, &self.lines[0..1], bounds, window);
8294 self.paint_lines(
8295 self.start_y + self.line_height,
8296 &self.lines[1..],
8297 bounds,
8298 window,
8299 );
8300 } else {
8301 self.paint_lines(self.start_y, &self.lines, bounds, window);
8302 }
8303 }
8304
8305 fn paint_lines(
8306 &self,
8307 start_y: Pixels,
8308 lines: &[HighlightedRangeLine],
8309 _bounds: Bounds<Pixels>,
8310 window: &mut Window,
8311 ) {
8312 if lines.is_empty() {
8313 return;
8314 }
8315
8316 let first_line = lines.first().unwrap();
8317 let last_line = lines.last().unwrap();
8318
8319 let first_top_left = point(first_line.start_x, start_y);
8320 let first_top_right = point(first_line.end_x, start_y);
8321
8322 let curve_height = point(Pixels::ZERO, self.corner_radius);
8323 let curve_width = |start_x: Pixels, end_x: Pixels| {
8324 let max = (end_x - start_x) / 2.;
8325 let width = if max < self.corner_radius {
8326 max
8327 } else {
8328 self.corner_radius
8329 };
8330
8331 point(width, Pixels::ZERO)
8332 };
8333
8334 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
8335 let mut builder = gpui::PathBuilder::fill();
8336 builder.move_to(first_top_right - top_curve_width);
8337 builder.curve_to(first_top_right + curve_height, first_top_right);
8338
8339 let mut iter = lines.iter().enumerate().peekable();
8340 while let Some((ix, line)) = iter.next() {
8341 let bottom_right = point(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
8342
8343 if let Some((_, next_line)) = iter.peek() {
8344 let next_top_right = point(next_line.end_x, bottom_right.y);
8345
8346 match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
8347 Ordering::Equal => {
8348 builder.line_to(bottom_right);
8349 }
8350 Ordering::Less => {
8351 let curve_width = curve_width(next_top_right.x, bottom_right.x);
8352 builder.line_to(bottom_right - curve_height);
8353 if self.corner_radius > Pixels::ZERO {
8354 builder.curve_to(bottom_right - curve_width, bottom_right);
8355 }
8356 builder.line_to(next_top_right + curve_width);
8357 if self.corner_radius > Pixels::ZERO {
8358 builder.curve_to(next_top_right + curve_height, next_top_right);
8359 }
8360 }
8361 Ordering::Greater => {
8362 let curve_width = curve_width(bottom_right.x, next_top_right.x);
8363 builder.line_to(bottom_right - curve_height);
8364 if self.corner_radius > Pixels::ZERO {
8365 builder.curve_to(bottom_right + curve_width, bottom_right);
8366 }
8367 builder.line_to(next_top_right - curve_width);
8368 if self.corner_radius > Pixels::ZERO {
8369 builder.curve_to(next_top_right + curve_height, next_top_right);
8370 }
8371 }
8372 }
8373 } else {
8374 let curve_width = curve_width(line.start_x, line.end_x);
8375 builder.line_to(bottom_right - curve_height);
8376 if self.corner_radius > Pixels::ZERO {
8377 builder.curve_to(bottom_right - curve_width, bottom_right);
8378 }
8379
8380 let bottom_left = point(line.start_x, bottom_right.y);
8381 builder.line_to(bottom_left + curve_width);
8382 if self.corner_radius > Pixels::ZERO {
8383 builder.curve_to(bottom_left - curve_height, bottom_left);
8384 }
8385 }
8386 }
8387
8388 if first_line.start_x > last_line.start_x {
8389 let curve_width = curve_width(last_line.start_x, first_line.start_x);
8390 let second_top_left = point(last_line.start_x, start_y + self.line_height);
8391 builder.line_to(second_top_left + curve_height);
8392 if self.corner_radius > Pixels::ZERO {
8393 builder.curve_to(second_top_left + curve_width, second_top_left);
8394 }
8395 let first_bottom_left = point(first_line.start_x, second_top_left.y);
8396 builder.line_to(first_bottom_left - curve_width);
8397 if self.corner_radius > Pixels::ZERO {
8398 builder.curve_to(first_bottom_left - curve_height, first_bottom_left);
8399 }
8400 }
8401
8402 builder.line_to(first_top_left + curve_height);
8403 if self.corner_radius > Pixels::ZERO {
8404 builder.curve_to(first_top_left + top_curve_width, first_top_left);
8405 }
8406 builder.line_to(first_top_right - top_curve_width);
8407
8408 if let Ok(path) = builder.build() {
8409 window.paint_path(path, self.color);
8410 }
8411 }
8412}
8413
8414enum CursorPopoverType {
8415 CodeContextMenu,
8416 EditPrediction,
8417}
8418
8419pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
8420 (delta.pow(1.2) / 100.0).min(px(3.0)).into()
8421}
8422
8423fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
8424 (delta.pow(1.2) / 300.0).into()
8425}
8426
8427pub fn register_action<T: Action>(
8428 editor: &Entity<Editor>,
8429 window: &mut Window,
8430 listener: impl Fn(&mut Editor, &T, &mut Window, &mut Context<Editor>) + 'static,
8431) {
8432 let editor = editor.clone();
8433 window.on_action(TypeId::of::<T>(), move |action, phase, window, cx| {
8434 let action = action.downcast_ref().unwrap();
8435 if phase == DispatchPhase::Bubble {
8436 editor.update(cx, |editor, cx| {
8437 listener(editor, action, window, cx);
8438 })
8439 }
8440 })
8441}
8442
8443fn compute_auto_height_layout(
8444 editor: &mut Editor,
8445 max_lines: usize,
8446 max_line_number_width: Pixels,
8447 known_dimensions: Size<Option<Pixels>>,
8448 available_width: AvailableSpace,
8449 window: &mut Window,
8450 cx: &mut Context<Editor>,
8451) -> Option<Size<Pixels>> {
8452 let width = known_dimensions.width.or({
8453 if let AvailableSpace::Definite(available_width) = available_width {
8454 Some(available_width)
8455 } else {
8456 None
8457 }
8458 })?;
8459 if let Some(height) = known_dimensions.height {
8460 return Some(size(width, height));
8461 }
8462
8463 let style = editor.style.as_ref().unwrap();
8464 let font_id = window.text_system().resolve_font(&style.text.font());
8465 let font_size = style.text.font_size.to_pixels(window.rem_size());
8466 let line_height = style.text.line_height_in_pixels(window.rem_size());
8467 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
8468
8469 let mut snapshot = editor.snapshot(window, cx);
8470 let gutter_dimensions = snapshot
8471 .gutter_dimensions(font_id, font_size, max_line_number_width, cx)
8472 .unwrap_or_default();
8473
8474 editor.gutter_dimensions = gutter_dimensions;
8475 let text_width = width - gutter_dimensions.width;
8476 let overscroll = size(em_width, px(0.));
8477
8478 let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width;
8479 if editor.set_wrap_width(Some(editor_width), cx) {
8480 snapshot = editor.snapshot(window, cx);
8481 }
8482
8483 let scroll_height = (snapshot.max_point().row().next_row().0 as f32) * line_height;
8484 let height = scroll_height
8485 .max(line_height)
8486 .min(line_height * max_lines as f32);
8487
8488 Some(size(width, height))
8489}
8490
8491#[cfg(test)]
8492mod tests {
8493 use super::*;
8494 use crate::{
8495 Editor, MultiBuffer,
8496 display_map::{BlockPlacement, BlockProperties},
8497 editor_tests::{init_test, update_test_language_settings},
8498 };
8499 use gpui::{TestAppContext, VisualTestContext};
8500 use language::language_settings;
8501 use log::info;
8502 use std::num::NonZeroU32;
8503 use util::test::sample_text;
8504
8505 #[gpui::test]
8506 fn test_shape_line_numbers(cx: &mut TestAppContext) {
8507 init_test(cx, |_| {});
8508 let window = cx.add_window(|window, cx| {
8509 let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
8510 Editor::new(EditorMode::full(), buffer, None, window, cx)
8511 });
8512
8513 let editor = window.root(cx).unwrap();
8514 let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
8515 let line_height = window
8516 .update(cx, |_, window, _| {
8517 style.text.line_height_in_pixels(window.rem_size())
8518 })
8519 .unwrap();
8520 let element = EditorElement::new(&editor, style);
8521 let snapshot = window
8522 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
8523 .unwrap();
8524
8525 let layouts = cx
8526 .update_window(*window, |_, window, cx| {
8527 element.layout_line_numbers(
8528 None,
8529 GutterDimensions {
8530 left_padding: Pixels::ZERO,
8531 right_padding: Pixels::ZERO,
8532 width: px(30.0),
8533 margin: Pixels::ZERO,
8534 git_blame_entries_width: None,
8535 },
8536 line_height,
8537 gpui::Point::default(),
8538 DisplayRow(0)..DisplayRow(6),
8539 &(0..6)
8540 .map(|row| RowInfo {
8541 buffer_row: Some(row),
8542 ..Default::default()
8543 })
8544 .collect::<Vec<_>>(),
8545 &BTreeMap::default(),
8546 Some(DisplayPoint::new(DisplayRow(0), 0)),
8547 &snapshot,
8548 window,
8549 cx,
8550 )
8551 })
8552 .unwrap();
8553 assert_eq!(layouts.len(), 6);
8554
8555 let relative_rows = window
8556 .update(cx, |editor, window, cx| {
8557 let snapshot = editor.snapshot(window, cx);
8558 element.calculate_relative_line_numbers(
8559 &snapshot,
8560 &(DisplayRow(0)..DisplayRow(6)),
8561 Some(DisplayRow(3)),
8562 )
8563 })
8564 .unwrap();
8565 assert_eq!(relative_rows[&DisplayRow(0)], 3);
8566 assert_eq!(relative_rows[&DisplayRow(1)], 2);
8567 assert_eq!(relative_rows[&DisplayRow(2)], 1);
8568 // current line has no relative number
8569 assert_eq!(relative_rows[&DisplayRow(4)], 1);
8570 assert_eq!(relative_rows[&DisplayRow(5)], 2);
8571
8572 // works if cursor is before screen
8573 let relative_rows = window
8574 .update(cx, |editor, window, cx| {
8575 let snapshot = editor.snapshot(window, cx);
8576 element.calculate_relative_line_numbers(
8577 &snapshot,
8578 &(DisplayRow(3)..DisplayRow(6)),
8579 Some(DisplayRow(1)),
8580 )
8581 })
8582 .unwrap();
8583 assert_eq!(relative_rows.len(), 3);
8584 assert_eq!(relative_rows[&DisplayRow(3)], 2);
8585 assert_eq!(relative_rows[&DisplayRow(4)], 3);
8586 assert_eq!(relative_rows[&DisplayRow(5)], 4);
8587
8588 // works if cursor is after screen
8589 let relative_rows = window
8590 .update(cx, |editor, window, cx| {
8591 let snapshot = editor.snapshot(window, cx);
8592 element.calculate_relative_line_numbers(
8593 &snapshot,
8594 &(DisplayRow(0)..DisplayRow(3)),
8595 Some(DisplayRow(6)),
8596 )
8597 })
8598 .unwrap();
8599 assert_eq!(relative_rows.len(), 3);
8600 assert_eq!(relative_rows[&DisplayRow(0)], 5);
8601 assert_eq!(relative_rows[&DisplayRow(1)], 4);
8602 assert_eq!(relative_rows[&DisplayRow(2)], 3);
8603 }
8604
8605 #[gpui::test]
8606 async fn test_vim_visual_selections(cx: &mut TestAppContext) {
8607 init_test(cx, |_| {});
8608
8609 let window = cx.add_window(|window, cx| {
8610 let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
8611 Editor::new(EditorMode::full(), buffer, None, window, cx)
8612 });
8613 let cx = &mut VisualTestContext::from_window(*window, cx);
8614 let editor = window.root(cx).unwrap();
8615 let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
8616
8617 window
8618 .update(cx, |editor, window, cx| {
8619 editor.cursor_shape = CursorShape::Block;
8620 editor.change_selections(None, window, cx, |s| {
8621 s.select_ranges([
8622 Point::new(0, 0)..Point::new(1, 0),
8623 Point::new(3, 2)..Point::new(3, 3),
8624 Point::new(5, 6)..Point::new(6, 0),
8625 ]);
8626 });
8627 })
8628 .unwrap();
8629
8630 let (_, state) = cx.draw(
8631 point(px(500.), px(500.)),
8632 size(px(500.), px(500.)),
8633 |_, _| EditorElement::new(&editor, style),
8634 );
8635
8636 assert_eq!(state.selections.len(), 1);
8637 let local_selections = &state.selections[0].1;
8638 assert_eq!(local_selections.len(), 3);
8639 // moves cursor back one line
8640 assert_eq!(
8641 local_selections[0].head,
8642 DisplayPoint::new(DisplayRow(0), 6)
8643 );
8644 assert_eq!(
8645 local_selections[0].range,
8646 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0)
8647 );
8648
8649 // moves cursor back one column
8650 assert_eq!(
8651 local_selections[1].range,
8652 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 3)
8653 );
8654 assert_eq!(
8655 local_selections[1].head,
8656 DisplayPoint::new(DisplayRow(3), 2)
8657 );
8658
8659 // leaves cursor on the max point
8660 assert_eq!(
8661 local_selections[2].range,
8662 DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(6), 0)
8663 );
8664 assert_eq!(
8665 local_selections[2].head,
8666 DisplayPoint::new(DisplayRow(6), 0)
8667 );
8668
8669 // active lines does not include 1 (even though the range of the selection does)
8670 assert_eq!(
8671 state.active_rows.keys().cloned().collect::<Vec<_>>(),
8672 vec![DisplayRow(0), DisplayRow(3), DisplayRow(5), DisplayRow(6)]
8673 );
8674 }
8675
8676 #[gpui::test]
8677 fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
8678 init_test(cx, |_| {});
8679
8680 let window = cx.add_window(|window, cx| {
8681 let buffer = MultiBuffer::build_simple("", cx);
8682 Editor::new(EditorMode::full(), buffer, None, window, cx)
8683 });
8684 let cx = &mut VisualTestContext::from_window(*window, cx);
8685 let editor = window.root(cx).unwrap();
8686 let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
8687 window
8688 .update(cx, |editor, window, cx| {
8689 editor.set_placeholder_text("hello", cx);
8690 editor.insert_blocks(
8691 [BlockProperties {
8692 style: BlockStyle::Fixed,
8693 placement: BlockPlacement::Above(Anchor::min()),
8694 height: Some(3),
8695 render: Arc::new(|cx| div().h(3. * cx.window.line_height()).into_any()),
8696 priority: 0,
8697 }],
8698 None,
8699 cx,
8700 );
8701
8702 // Blur the editor so that it displays placeholder text.
8703 window.blur();
8704 })
8705 .unwrap();
8706
8707 let (_, state) = cx.draw(
8708 point(px(500.), px(500.)),
8709 size(px(500.), px(500.)),
8710 |_, _| EditorElement::new(&editor, style),
8711 );
8712 assert_eq!(state.position_map.line_layouts.len(), 4);
8713 assert_eq!(state.line_numbers.len(), 1);
8714 assert_eq!(
8715 state
8716 .line_numbers
8717 .get(&MultiBufferRow(0))
8718 .map(|line_number| line_number.shaped_line.text.as_ref()),
8719 Some("1")
8720 );
8721 }
8722
8723 #[gpui::test]
8724 fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
8725 const TAB_SIZE: u32 = 4;
8726
8727 let input_text = "\t \t|\t| a b";
8728 let expected_invisibles = vec![
8729 Invisible::Tab {
8730 line_start_offset: 0,
8731 line_end_offset: TAB_SIZE as usize,
8732 },
8733 Invisible::Whitespace {
8734 line_offset: TAB_SIZE as usize,
8735 },
8736 Invisible::Tab {
8737 line_start_offset: TAB_SIZE as usize + 1,
8738 line_end_offset: TAB_SIZE as usize * 2,
8739 },
8740 Invisible::Tab {
8741 line_start_offset: TAB_SIZE as usize * 2 + 1,
8742 line_end_offset: TAB_SIZE as usize * 3,
8743 },
8744 Invisible::Whitespace {
8745 line_offset: TAB_SIZE as usize * 3 + 1,
8746 },
8747 Invisible::Whitespace {
8748 line_offset: TAB_SIZE as usize * 3 + 3,
8749 },
8750 ];
8751 assert_eq!(
8752 expected_invisibles.len(),
8753 input_text
8754 .chars()
8755 .filter(|initial_char| initial_char.is_whitespace())
8756 .count(),
8757 "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
8758 );
8759
8760 for show_line_numbers in [true, false] {
8761 init_test(cx, |s| {
8762 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
8763 s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
8764 });
8765
8766 let actual_invisibles = collect_invisibles_from_new_editor(
8767 cx,
8768 EditorMode::full(),
8769 input_text,
8770 px(500.0),
8771 show_line_numbers,
8772 );
8773
8774 assert_eq!(expected_invisibles, actual_invisibles);
8775 }
8776 }
8777
8778 #[gpui::test]
8779 fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
8780 init_test(cx, |s| {
8781 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
8782 s.defaults.tab_size = NonZeroU32::new(4);
8783 });
8784
8785 for editor_mode_without_invisibles in [
8786 EditorMode::SingleLine { auto_width: false },
8787 EditorMode::AutoHeight { max_lines: 100 },
8788 ] {
8789 for show_line_numbers in [true, false] {
8790 let invisibles = collect_invisibles_from_new_editor(
8791 cx,
8792 editor_mode_without_invisibles,
8793 "\t\t\t| | a b",
8794 px(500.0),
8795 show_line_numbers,
8796 );
8797 assert!(
8798 invisibles.is_empty(),
8799 "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}"
8800 );
8801 }
8802 }
8803 }
8804
8805 #[gpui::test]
8806 fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
8807 let tab_size = 4;
8808 let input_text = "a\tbcd ".repeat(9);
8809 let repeated_invisibles = [
8810 Invisible::Tab {
8811 line_start_offset: 1,
8812 line_end_offset: tab_size as usize,
8813 },
8814 Invisible::Whitespace {
8815 line_offset: tab_size as usize + 3,
8816 },
8817 Invisible::Whitespace {
8818 line_offset: tab_size as usize + 4,
8819 },
8820 Invisible::Whitespace {
8821 line_offset: tab_size as usize + 5,
8822 },
8823 Invisible::Whitespace {
8824 line_offset: tab_size as usize + 6,
8825 },
8826 Invisible::Whitespace {
8827 line_offset: tab_size as usize + 7,
8828 },
8829 ];
8830 let expected_invisibles = std::iter::once(repeated_invisibles)
8831 .cycle()
8832 .take(9)
8833 .flatten()
8834 .collect::<Vec<_>>();
8835 assert_eq!(
8836 expected_invisibles.len(),
8837 input_text
8838 .chars()
8839 .filter(|initial_char| initial_char.is_whitespace())
8840 .count(),
8841 "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
8842 );
8843 info!("Expected invisibles: {expected_invisibles:?}");
8844
8845 init_test(cx, |_| {});
8846
8847 // Put the same string with repeating whitespace pattern into editors of various size,
8848 // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
8849 let resize_step = 10.0;
8850 let mut editor_width = 200.0;
8851 while editor_width <= 1000.0 {
8852 for show_line_numbers in [true, false] {
8853 update_test_language_settings(cx, |s| {
8854 s.defaults.tab_size = NonZeroU32::new(tab_size);
8855 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
8856 s.defaults.preferred_line_length = Some(editor_width as u32);
8857 s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
8858 });
8859
8860 let actual_invisibles = collect_invisibles_from_new_editor(
8861 cx,
8862 EditorMode::full(),
8863 &input_text,
8864 px(editor_width),
8865 show_line_numbers,
8866 );
8867
8868 // Whatever the editor size is, ensure it has the same invisible kinds in the same order
8869 // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
8870 let mut i = 0;
8871 for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
8872 i = actual_index;
8873 match expected_invisibles.get(i) {
8874 Some(expected_invisible) => match (expected_invisible, actual_invisible) {
8875 (Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
8876 | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
8877 _ => {
8878 panic!(
8879 "At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}"
8880 )
8881 }
8882 },
8883 None => {
8884 panic!("Unexpected extra invisible {actual_invisible:?} at index {i}")
8885 }
8886 }
8887 }
8888 let missing_expected_invisibles = &expected_invisibles[i + 1..];
8889 assert!(
8890 missing_expected_invisibles.is_empty(),
8891 "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
8892 );
8893
8894 editor_width += resize_step;
8895 }
8896 }
8897 }
8898
8899 fn collect_invisibles_from_new_editor(
8900 cx: &mut TestAppContext,
8901 editor_mode: EditorMode,
8902 input_text: &str,
8903 editor_width: Pixels,
8904 show_line_numbers: bool,
8905 ) -> Vec<Invisible> {
8906 info!(
8907 "Creating editor with mode {editor_mode:?}, width {}px and text '{input_text}'",
8908 editor_width.0
8909 );
8910 let window = cx.add_window(|window, cx| {
8911 let buffer = MultiBuffer::build_simple(input_text, cx);
8912 Editor::new(editor_mode, buffer, None, window, cx)
8913 });
8914 let cx = &mut VisualTestContext::from_window(*window, cx);
8915 let editor = window.root(cx).unwrap();
8916
8917 let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
8918 window
8919 .update(cx, |editor, _, cx| {
8920 editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
8921 editor.set_wrap_width(Some(editor_width), cx);
8922 editor.set_show_line_numbers(show_line_numbers, cx);
8923 })
8924 .unwrap();
8925 let (_, state) = cx.draw(
8926 point(px(500.), px(500.)),
8927 size(px(500.), px(500.)),
8928 |_, _| EditorElement::new(&editor, style),
8929 );
8930 state
8931 .position_map
8932 .line_layouts
8933 .iter()
8934 .flat_map(|line_with_invisibles| &line_with_invisibles.invisibles)
8935 .cloned()
8936 .collect()
8937 }
8938}