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