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