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