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