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