1use crate::{
2 display_map::{
3 BlockContext, BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, ToDisplayPoint,
4 TransformBlock,
5 },
6 editor_settings::ShowScrollbar,
7 git::{diff_hunk_to_display, DisplayDiffHunk},
8 hover_popover::{
9 self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
10 },
11 link_go_to_definition::{
12 go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
13 update_inlay_link_and_hover_points, GoToDefinitionTrigger,
14 },
15 scroll::scroll_amount::ScrollAmount,
16 CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
17 HalfPageDown, HalfPageUp, LineDown, LineUp, MoveDown, OpenExcerpts, PageDown, PageUp, Point,
18 SelectPhase, Selection, SoftWrap, ToPoint, MAX_LINE_LEN,
19};
20use anyhow::Result;
21use collections::{BTreeMap, HashMap};
22use gpui::{
23 div, point, px, relative, size, transparent_black, Action, AnyElement, AsyncWindowContext,
24 AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element,
25 ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement,
26 IntoElement, LineLayout, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
27 ParentElement, Pixels, RenderOnce, ScrollWheelEvent, ShapedLine, SharedString, Size,
28 StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View,
29 ViewContext, WeakView, WindowContext, WrappedLine,
30};
31use itertools::Itertools;
32use language::language_settings::ShowWhitespaceSetting;
33use multi_buffer::Anchor;
34use project::{
35 project_settings::{GitGutterSetting, ProjectSettings},
36 ProjectPath,
37};
38use settings::Settings;
39use smallvec::SmallVec;
40use std::{
41 any::TypeId,
42 borrow::Cow,
43 cmp::{self, Ordering},
44 fmt::Write,
45 iter,
46 ops::Range,
47 sync::Arc,
48};
49use sum_tree::Bias;
50use theme::{ActiveTheme, PlayerColor};
51use ui::prelude::*;
52use ui::{h_stack, IconButton, Tooltip};
53use util::ResultExt;
54use workspace::item::Item;
55
56enum FoldMarkers {}
57
58struct SelectionLayout {
59 head: DisplayPoint,
60 cursor_shape: CursorShape,
61 is_newest: bool,
62 is_local: bool,
63 range: Range<DisplayPoint>,
64 active_rows: Range<u32>,
65}
66
67impl SelectionLayout {
68 fn new<T: ToPoint + ToDisplayPoint + Clone>(
69 selection: Selection<T>,
70 line_mode: bool,
71 cursor_shape: CursorShape,
72 map: &DisplaySnapshot,
73 is_newest: bool,
74 is_local: bool,
75 ) -> Self {
76 let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
77 let display_selection = point_selection.map(|p| p.to_display_point(map));
78 let mut range = display_selection.range();
79 let mut head = display_selection.head();
80 let mut active_rows = map.prev_line_boundary(point_selection.start).1.row()
81 ..map.next_line_boundary(point_selection.end).1.row();
82
83 // vim visual line mode
84 if line_mode {
85 let point_range = map.expand_to_line(point_selection.range());
86 range = point_range.start.to_display_point(map)..point_range.end.to_display_point(map);
87 }
88
89 // any vim visual mode (including line mode)
90 if cursor_shape == CursorShape::Block && !range.is_empty() && !selection.reversed {
91 if head.column() > 0 {
92 head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left)
93 } else if head.row() > 0 && head != map.max_point() {
94 head = map.clip_point(
95 DisplayPoint::new(head.row() - 1, map.line_len(head.row() - 1)),
96 Bias::Left,
97 );
98 // updating range.end is a no-op unless you're cursor is
99 // on the newline containing a multi-buffer divider
100 // in which case the clip_point may have moved the head up
101 // an additional row.
102 range.end = DisplayPoint::new(head.row() + 1, 0);
103 active_rows.end = head.row();
104 }
105 }
106
107 Self {
108 head,
109 cursor_shape,
110 is_newest,
111 is_local,
112 range,
113 active_rows,
114 }
115 }
116}
117
118pub struct EditorElement {
119 editor: View<Editor>,
120 style: EditorStyle,
121}
122
123impl EditorElement {
124 pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {
125 Self {
126 editor: editor.clone(),
127 style,
128 }
129 }
130
131 fn register_actions(&self, cx: &mut WindowContext) {
132 let view = &self.editor;
133 self.editor.update(cx, |editor, cx| {
134 for action in editor.editor_actions.iter() {
135 (action)(cx)
136 }
137 });
138 register_action(view, cx, Editor::move_left);
139 register_action(view, cx, Editor::move_right);
140 register_action(view, cx, Editor::move_down);
141 register_action(view, cx, Editor::move_up);
142 register_action(view, cx, Editor::cancel);
143 register_action(view, cx, Editor::newline);
144 register_action(view, cx, Editor::newline_above);
145 register_action(view, cx, Editor::newline_below);
146 register_action(view, cx, Editor::backspace);
147 register_action(view, cx, Editor::delete);
148 register_action(view, cx, Editor::tab);
149 register_action(view, cx, Editor::tab_prev);
150 register_action(view, cx, Editor::indent);
151 register_action(view, cx, Editor::outdent);
152 register_action(view, cx, Editor::delete_line);
153 register_action(view, cx, Editor::join_lines);
154 register_action(view, cx, Editor::sort_lines_case_sensitive);
155 register_action(view, cx, Editor::sort_lines_case_insensitive);
156 register_action(view, cx, Editor::reverse_lines);
157 register_action(view, cx, Editor::shuffle_lines);
158 register_action(view, cx, Editor::convert_to_upper_case);
159 register_action(view, cx, Editor::convert_to_lower_case);
160 register_action(view, cx, Editor::convert_to_title_case);
161 register_action(view, cx, Editor::convert_to_snake_case);
162 register_action(view, cx, Editor::convert_to_kebab_case);
163 register_action(view, cx, Editor::convert_to_upper_camel_case);
164 register_action(view, cx, Editor::convert_to_lower_camel_case);
165 register_action(view, cx, Editor::delete_to_previous_word_start);
166 register_action(view, cx, Editor::delete_to_previous_subword_start);
167 register_action(view, cx, Editor::delete_to_next_word_end);
168 register_action(view, cx, Editor::delete_to_next_subword_end);
169 register_action(view, cx, Editor::delete_to_beginning_of_line);
170 register_action(view, cx, Editor::delete_to_end_of_line);
171 register_action(view, cx, Editor::cut_to_end_of_line);
172 register_action(view, cx, Editor::duplicate_line);
173 register_action(view, cx, Editor::move_line_up);
174 register_action(view, cx, Editor::move_line_down);
175 register_action(view, cx, Editor::transpose);
176 register_action(view, cx, Editor::cut);
177 register_action(view, cx, Editor::copy);
178 register_action(view, cx, Editor::paste);
179 register_action(view, cx, Editor::undo);
180 register_action(view, cx, Editor::redo);
181 register_action(view, cx, Editor::move_page_up);
182 register_action(view, cx, Editor::move_page_down);
183 register_action(view, cx, Editor::next_screen);
184 register_action(view, cx, Editor::scroll_cursor_top);
185 register_action(view, cx, Editor::scroll_cursor_center);
186 register_action(view, cx, Editor::scroll_cursor_bottom);
187 register_action(view, cx, |editor, _: &LineDown, cx| {
188 editor.scroll_screen(&ScrollAmount::Line(1.), cx)
189 });
190 register_action(view, cx, |editor, _: &LineUp, cx| {
191 editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
192 });
193 register_action(view, cx, |editor, _: &HalfPageDown, cx| {
194 editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
195 });
196 register_action(view, cx, |editor, _: &HalfPageUp, cx| {
197 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
198 });
199 register_action(view, cx, |editor, _: &PageDown, cx| {
200 editor.scroll_screen(&ScrollAmount::Page(1.), cx)
201 });
202 register_action(view, cx, |editor, _: &PageUp, cx| {
203 editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
204 });
205 register_action(view, cx, Editor::move_to_previous_word_start);
206 register_action(view, cx, Editor::move_to_previous_subword_start);
207 register_action(view, cx, Editor::move_to_next_word_end);
208 register_action(view, cx, Editor::move_to_next_subword_end);
209 register_action(view, cx, Editor::move_to_beginning_of_line);
210 register_action(view, cx, Editor::move_to_end_of_line);
211 register_action(view, cx, Editor::move_to_start_of_paragraph);
212 register_action(view, cx, Editor::move_to_end_of_paragraph);
213 register_action(view, cx, Editor::move_to_beginning);
214 register_action(view, cx, Editor::move_to_end);
215 register_action(view, cx, Editor::select_up);
216 register_action(view, cx, Editor::select_down);
217 register_action(view, cx, Editor::select_left);
218 register_action(view, cx, Editor::select_right);
219 register_action(view, cx, Editor::select_to_previous_word_start);
220 register_action(view, cx, Editor::select_to_previous_subword_start);
221 register_action(view, cx, Editor::select_to_next_word_end);
222 register_action(view, cx, Editor::select_to_next_subword_end);
223 register_action(view, cx, Editor::select_to_beginning_of_line);
224 register_action(view, cx, Editor::select_to_end_of_line);
225 register_action(view, cx, Editor::select_to_start_of_paragraph);
226 register_action(view, cx, Editor::select_to_end_of_paragraph);
227 register_action(view, cx, Editor::select_to_beginning);
228 register_action(view, cx, Editor::select_to_end);
229 register_action(view, cx, Editor::select_all);
230 register_action(view, cx, |editor, action, cx| {
231 editor.select_all_matches(action, cx).log_err();
232 });
233 register_action(view, cx, Editor::select_line);
234 register_action(view, cx, Editor::split_selection_into_lines);
235 register_action(view, cx, Editor::add_selection_above);
236 register_action(view, cx, Editor::add_selection_below);
237 register_action(view, cx, |editor, action, cx| {
238 editor.select_next(action, cx).log_err();
239 });
240 register_action(view, cx, |editor, action, cx| {
241 editor.select_previous(action, cx).log_err();
242 });
243 register_action(view, cx, Editor::toggle_comments);
244 register_action(view, cx, Editor::select_larger_syntax_node);
245 register_action(view, cx, Editor::select_smaller_syntax_node);
246 register_action(view, cx, Editor::move_to_enclosing_bracket);
247 register_action(view, cx, Editor::undo_selection);
248 register_action(view, cx, Editor::redo_selection);
249 register_action(view, cx, Editor::go_to_diagnostic);
250 register_action(view, cx, Editor::go_to_prev_diagnostic);
251 register_action(view, cx, Editor::go_to_hunk);
252 register_action(view, cx, Editor::go_to_prev_hunk);
253 register_action(view, cx, Editor::go_to_definition);
254 register_action(view, cx, Editor::go_to_definition_split);
255 register_action(view, cx, Editor::go_to_type_definition);
256 register_action(view, cx, Editor::go_to_type_definition_split);
257 register_action(view, cx, Editor::fold);
258 register_action(view, cx, Editor::fold_at);
259 register_action(view, cx, Editor::unfold_lines);
260 register_action(view, cx, Editor::unfold_at);
261 register_action(view, cx, Editor::fold_selected_ranges);
262 register_action(view, cx, Editor::show_completions);
263 register_action(view, cx, Editor::toggle_code_actions);
264 register_action(view, cx, Editor::open_excerpts);
265 register_action(view, cx, Editor::toggle_soft_wrap);
266 register_action(view, cx, Editor::toggle_inlay_hints);
267 register_action(view, cx, hover_popover::hover);
268 register_action(view, cx, Editor::reveal_in_finder);
269 register_action(view, cx, Editor::copy_path);
270 register_action(view, cx, Editor::copy_relative_path);
271 register_action(view, cx, Editor::copy_highlight_json);
272 register_action(view, cx, |editor, action, cx| {
273 editor
274 .format(action, cx)
275 .map(|task| task.detach_and_log_err(cx));
276 });
277 register_action(view, cx, Editor::restart_language_server);
278 register_action(view, cx, Editor::show_character_palette);
279 register_action(view, cx, |editor, action, cx| {
280 editor
281 .confirm_completion(action, cx)
282 .map(|task| task.detach_and_log_err(cx));
283 });
284 register_action(view, cx, |editor, action, cx| {
285 editor
286 .confirm_code_action(action, cx)
287 .map(|task| task.detach_and_log_err(cx));
288 });
289 register_action(view, cx, |editor, action, cx| {
290 editor
291 .rename(action, cx)
292 .map(|task| task.detach_and_log_err(cx));
293 });
294 register_action(view, cx, |editor, action, cx| {
295 editor
296 .confirm_rename(action, cx)
297 .map(|task| task.detach_and_log_err(cx));
298 });
299 register_action(view, cx, |editor, action, cx| {
300 editor
301 .find_all_references(action, cx)
302 .map(|task| task.detach_and_log_err(cx));
303 });
304 register_action(view, cx, Editor::next_copilot_suggestion);
305 register_action(view, cx, Editor::previous_copilot_suggestion);
306 register_action(view, cx, Editor::copilot_suggest);
307 register_action(view, cx, Editor::context_menu_first);
308 register_action(view, cx, Editor::context_menu_prev);
309 register_action(view, cx, Editor::context_menu_next);
310 register_action(view, cx, Editor::context_menu_last);
311 }
312
313 fn mouse_down(
314 editor: &mut Editor,
315 event: &MouseDownEvent,
316 position_map: &PositionMap,
317 text_bounds: Bounds<Pixels>,
318 gutter_bounds: Bounds<Pixels>,
319 stacking_order: &StackingOrder,
320 cx: &mut ViewContext<Editor>,
321 ) -> bool {
322 let mut click_count = event.click_count;
323 let modifiers = event.modifiers;
324
325 if gutter_bounds.contains_point(&event.position) {
326 click_count = 3; // Simulate triple-click when clicking the gutter to select lines
327 } else if !text_bounds.contains_point(&event.position) {
328 return false;
329 }
330 if !cx.was_top_layer(&event.position, stacking_order) {
331 return false;
332 }
333
334 let point_for_position = position_map.point_for_position(text_bounds, event.position);
335 let position = point_for_position.previous_valid;
336 if modifiers.shift && modifiers.alt {
337 editor.select(
338 SelectPhase::BeginColumnar {
339 position,
340 goal_column: point_for_position.exact_unclipped.column(),
341 },
342 cx,
343 );
344 } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.command {
345 editor.select(
346 SelectPhase::Extend {
347 position,
348 click_count,
349 },
350 cx,
351 );
352 } else {
353 editor.select(
354 SelectPhase::Begin {
355 position,
356 add: modifiers.alt,
357 click_count,
358 },
359 cx,
360 );
361 }
362
363 true
364 }
365
366 // fn mouse_right_down(
367 // editor: &mut Editor,
368 // position: gpui::Point<Pixels>,
369 // position_map: &PositionMap,
370 // text_bounds: Bounds<Pixels>,
371 // cx: &mut EventContext<Editor>,
372 // ) -> bool {
373 // if !text_bounds.contains_point(position) {
374 // return false;
375 // }
376 // let point_for_position = position_map.point_for_position(text_bounds, position);
377 // mouse_context_menu::deploy_context_menu(
378 // editor,
379 // position,
380 // point_for_position.previous_valid,
381 // cx,
382 // );
383 // true
384 // }
385
386 fn mouse_up(
387 editor: &mut Editor,
388 event: &MouseUpEvent,
389 position_map: &PositionMap,
390 text_bounds: Bounds<Pixels>,
391 stacking_order: &StackingOrder,
392 cx: &mut ViewContext<Editor>,
393 ) -> bool {
394 let end_selection = editor.has_pending_selection();
395 let pending_nonempty_selections = editor.has_pending_nonempty_selection();
396
397 if end_selection {
398 editor.select(SelectPhase::End, cx);
399 }
400
401 if !pending_nonempty_selections
402 && event.modifiers.command
403 && text_bounds.contains_point(&event.position)
404 && cx.was_top_layer(&event.position, stacking_order)
405 {
406 let point = position_map.point_for_position(text_bounds, event.position);
407 let could_be_inlay = point.as_valid().is_none();
408 let split = event.modifiers.alt;
409 if event.modifiers.shift || could_be_inlay {
410 go_to_fetched_type_definition(editor, point, split, cx);
411 } else {
412 go_to_fetched_definition(editor, point, split, cx);
413 }
414
415 return true;
416 }
417
418 end_selection
419 }
420
421 fn mouse_moved(
422 editor: &mut Editor,
423 event: &MouseMoveEvent,
424 position_map: &PositionMap,
425 text_bounds: Bounds<Pixels>,
426 gutter_bounds: Bounds<Pixels>,
427 stacking_order: &StackingOrder,
428 cx: &mut ViewContext<Editor>,
429 ) -> bool {
430 let modifiers = event.modifiers;
431 if editor.has_pending_selection() && event.pressed_button == Some(MouseButton::Left) {
432 let point_for_position = position_map.point_for_position(text_bounds, event.position);
433 let mut scroll_delta = gpui::Point::<f32>::zero();
434 let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
435 let top = text_bounds.origin.y + vertical_margin;
436 let bottom = text_bounds.lower_left().y - vertical_margin;
437 if event.position.y < top {
438 scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y);
439 }
440 if event.position.y > bottom {
441 scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
442 }
443
444 let horizontal_margin = position_map.line_height.min(text_bounds.size.width / 3.0);
445 let left = text_bounds.origin.x + horizontal_margin;
446 let right = text_bounds.upper_right().x - horizontal_margin;
447 if event.position.x < left {
448 scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
449 }
450 if event.position.x > right {
451 scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
452 }
453
454 editor.select(
455 SelectPhase::Update {
456 position: point_for_position.previous_valid,
457 goal_column: point_for_position.exact_unclipped.column(),
458 scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
459 .clamp(&gpui::Point::zero(), &position_map.scroll_max),
460 },
461 cx,
462 );
463 }
464
465 let text_hovered = text_bounds.contains_point(&event.position);
466 let gutter_hovered = gutter_bounds.contains_point(&event.position);
467 let was_top = cx.was_top_layer(&event.position, stacking_order);
468
469 editor.set_gutter_hovered(gutter_hovered, cx);
470
471 // Don't trigger hover popover if mouse is hovering over context menu
472 if text_hovered && was_top {
473 let point_for_position = position_map.point_for_position(text_bounds, event.position);
474
475 match point_for_position.as_valid() {
476 Some(point) => {
477 update_go_to_definition_link(
478 editor,
479 Some(GoToDefinitionTrigger::Text(point)),
480 modifiers.command,
481 modifiers.shift,
482 cx,
483 );
484 hover_at(editor, Some(point), cx);
485 }
486 None => {
487 update_inlay_link_and_hover_points(
488 &position_map.snapshot,
489 point_for_position,
490 editor,
491 modifiers.command,
492 modifiers.shift,
493 cx,
494 );
495 }
496 }
497
498 true
499 } else {
500 update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
501 hover_at(editor, None, cx);
502 gutter_hovered && was_top
503 }
504 }
505
506 fn scroll(
507 editor: &mut Editor,
508 event: &ScrollWheelEvent,
509 position_map: &PositionMap,
510 bounds: &InteractiveBounds,
511 cx: &mut ViewContext<Editor>,
512 ) -> bool {
513 if !bounds.visibly_contains(&event.position, cx) {
514 return false;
515 }
516
517 let line_height = position_map.line_height;
518 let max_glyph_width = position_map.em_width;
519 let (delta, axis) = match event.delta {
520 gpui::ScrollDelta::Pixels(mut pixels) => {
521 //Trackpad
522 let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
523 (pixels, axis)
524 }
525
526 gpui::ScrollDelta::Lines(lines) => {
527 //Not trackpad
528 let pixels = point(lines.x * max_glyph_width, lines.y * line_height);
529 (pixels, None)
530 }
531 };
532
533 let scroll_position = position_map.snapshot.scroll_position();
534 let x = f32::from((scroll_position.x * max_glyph_width - delta.x) / max_glyph_width);
535 let y = f32::from((scroll_position.y * line_height - delta.y) / line_height);
536 let scroll_position = point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
537 editor.scroll(scroll_position, axis, cx);
538
539 true
540 }
541
542 fn paint_background(
543 &self,
544 gutter_bounds: Bounds<Pixels>,
545 text_bounds: Bounds<Pixels>,
546 layout: &LayoutState,
547 cx: &mut WindowContext,
548 ) {
549 let bounds = gutter_bounds.union(&text_bounds);
550 let scroll_top =
551 layout.position_map.snapshot.scroll_position().y * layout.position_map.line_height;
552 let gutter_bg = cx.theme().colors().editor_gutter_background;
553 cx.paint_quad(
554 gutter_bounds,
555 Corners::default(),
556 gutter_bg,
557 Edges::default(),
558 transparent_black(),
559 );
560 cx.paint_quad(
561 text_bounds,
562 Corners::default(),
563 self.style.background,
564 Edges::default(),
565 transparent_black(),
566 );
567
568 if let EditorMode::Full = layout.mode {
569 let mut active_rows = layout.active_rows.iter().peekable();
570 while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
571 let mut end_row = *start_row;
572 while active_rows.peek().map_or(false, |r| {
573 *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
574 }) {
575 active_rows.next().unwrap();
576 end_row += 1;
577 }
578
579 if !contains_non_empty_selection {
580 let origin = point(
581 bounds.origin.x,
582 bounds.origin.y + (layout.position_map.line_height * *start_row as f32)
583 - scroll_top,
584 );
585 let size = size(
586 bounds.size.width,
587 layout.position_map.line_height * (end_row - start_row + 1) as f32,
588 );
589 let active_line_bg = cx.theme().colors().editor_active_line_background;
590 cx.paint_quad(
591 Bounds { origin, size },
592 Corners::default(),
593 active_line_bg,
594 Edges::default(),
595 transparent_black(),
596 );
597 }
598 }
599
600 if let Some(highlighted_rows) = &layout.highlighted_rows {
601 let origin = point(
602 bounds.origin.x,
603 bounds.origin.y
604 + (layout.position_map.line_height * highlighted_rows.start as f32)
605 - scroll_top,
606 );
607 let size = size(
608 bounds.size.width,
609 layout.position_map.line_height * highlighted_rows.len() as f32,
610 );
611 let highlighted_line_bg = cx.theme().colors().editor_highlighted_line_background;
612 cx.paint_quad(
613 Bounds { origin, size },
614 Corners::default(),
615 highlighted_line_bg,
616 Edges::default(),
617 transparent_black(),
618 );
619 }
620
621 let scroll_left =
622 layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
623
624 for (wrap_position, active) in layout.wrap_guides.iter() {
625 let x = (text_bounds.origin.x + *wrap_position + layout.position_map.em_width / 2.)
626 - scroll_left;
627
628 if x < text_bounds.origin.x
629 || (layout.show_scrollbars && x > self.scrollbar_left(&bounds))
630 {
631 continue;
632 }
633
634 let color = if *active {
635 cx.theme().colors().editor_active_wrap_guide
636 } else {
637 cx.theme().colors().editor_wrap_guide
638 };
639 cx.paint_quad(
640 Bounds {
641 origin: point(x, text_bounds.origin.y),
642 size: size(px(1.), text_bounds.size.height),
643 },
644 Corners::default(),
645 color,
646 Edges::default(),
647 transparent_black(),
648 );
649 }
650 }
651 }
652
653 fn paint_gutter(
654 &mut self,
655 bounds: Bounds<Pixels>,
656 layout: &mut LayoutState,
657 cx: &mut WindowContext,
658 ) {
659 let line_height = layout.position_map.line_height;
660
661 let scroll_position = layout.position_map.snapshot.scroll_position();
662 let scroll_top = scroll_position.y * line_height;
663
664 let show_gutter = matches!(
665 ProjectSettings::get_global(cx).git.git_gutter,
666 Some(GitGutterSetting::TrackedFiles)
667 );
668
669 if show_gutter {
670 Self::paint_diff_hunks(bounds, layout, cx);
671 }
672
673 for (ix, line) in layout.line_numbers.iter().enumerate() {
674 if let Some(line) = line {
675 let line_origin = bounds.origin
676 + point(
677 bounds.size.width - line.width - layout.gutter_padding,
678 ix as f32 * line_height - (scroll_top % line_height),
679 );
680
681 line.paint(line_origin, line_height, cx);
682 }
683 }
684
685 for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() {
686 if let Some(mut fold_indicator) = fold_indicator {
687 let mut fold_indicator = fold_indicator.into_any_element();
688 let available_space = size(
689 AvailableSpace::MinContent,
690 AvailableSpace::Definite(line_height * 0.55),
691 );
692 let fold_indicator_size = fold_indicator.measure(available_space, cx);
693
694 let position = point(
695 bounds.size.width - layout.gutter_padding,
696 ix as f32 * line_height - (scroll_top % line_height),
697 );
698 let centering_offset = point(
699 (layout.gutter_padding + layout.gutter_margin - fold_indicator_size.width) / 2.,
700 (line_height - fold_indicator_size.height) / 2.,
701 );
702 let origin = bounds.origin + position + centering_offset;
703 fold_indicator.draw(origin, available_space, cx);
704 }
705 }
706
707 if let Some(indicator) = layout.code_actions_indicator.take() {
708 let mut button = indicator.button.into_any_element();
709 let available_space = size(
710 AvailableSpace::MinContent,
711 AvailableSpace::Definite(line_height),
712 );
713 let indicator_size = button.measure(available_space, cx);
714
715 let mut x = Pixels::ZERO;
716 let mut y = indicator.row as f32 * line_height - scroll_top;
717 // Center indicator.
718 x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.;
719 y += (line_height - indicator_size.height) / 2.;
720
721 button.draw(bounds.origin + point(x, y), available_space, cx);
722 }
723 }
724
725 fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
726 // todo!()
727 // let diff_style = &theme::current(cx).editor.diff.clone();
728 // let line_height = layout.position_map.line_height;
729
730 // let scroll_position = layout.position_map.snapshot.scroll_position();
731 // let scroll_top = scroll_position.y * line_height;
732
733 // for hunk in &layout.display_hunks {
734 // let (display_row_range, status) = match hunk {
735 // //TODO: This rendering is entirely a horrible hack
736 // &DisplayDiffHunk::Folded { display_row: row } => {
737 // let start_y = row as f32 * line_height - scroll_top;
738 // let end_y = start_y + line_height;
739
740 // let width = diff_style.removed_width_em * line_height;
741 // let highlight_origin = bounds.origin + point(-width, start_y);
742 // let highlight_size = point(width * 2., end_y - start_y);
743 // let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
744
745 // cx.paint_quad(Quad {
746 // bounds: highlight_bounds,
747 // background: Some(diff_style.modified),
748 // border: Border::new(0., Color::transparent_black()).into(),
749 // corner_radii: (1. * line_height).into(),
750 // });
751
752 // continue;
753 // }
754
755 // DisplayDiffHunk::Unfolded {
756 // display_row_range,
757 // status,
758 // } => (display_row_range, status),
759 // };
760
761 // let color = match status {
762 // DiffHunkStatus::Added => diff_style.inserted,
763 // DiffHunkStatus::Modified => diff_style.modified,
764
765 // //TODO: This rendering is entirely a horrible hack
766 // DiffHunkStatus::Removed => {
767 // let row = display_row_range.start;
768
769 // let offset = line_height / 2.;
770 // let start_y = row as f32 * line_height - offset - scroll_top;
771 // let end_y = start_y + line_height;
772
773 // let width = diff_style.removed_width_em * line_height;
774 // let highlight_origin = bounds.origin + point(-width, start_y);
775 // let highlight_size = point(width * 2., end_y - start_y);
776 // let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
777
778 // cx.paint_quad(Quad {
779 // bounds: highlight_bounds,
780 // background: Some(diff_style.deleted),
781 // border: Border::new(0., Color::transparent_black()).into(),
782 // corner_radii: (1. * line_height).into(),
783 // });
784
785 // continue;
786 // }
787 // };
788
789 // let start_row = display_row_range.start;
790 // let end_row = display_row_range.end;
791
792 // let start_y = start_row as f32 * line_height - scroll_top;
793 // let end_y = end_row as f32 * line_height - scroll_top;
794
795 // let width = diff_style.width_em * line_height;
796 // let highlight_origin = bounds.origin + point(-width, start_y);
797 // let highlight_size = point(width * 2., end_y - start_y);
798 // let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
799
800 // cx.paint_quad(Quad {
801 // bounds: highlight_bounds,
802 // background: Some(color),
803 // border: Border::new(0., Color::transparent_black()).into(),
804 // corner_radii: (diff_style.corner_radius * line_height).into(),
805 // });
806 // }
807 }
808
809 fn paint_text(
810 &mut self,
811 text_bounds: Bounds<Pixels>,
812 layout: &mut LayoutState,
813 cx: &mut WindowContext,
814 ) {
815 let scroll_position = layout.position_map.snapshot.scroll_position();
816 let start_row = layout.visible_display_row_range.start;
817 let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
818 let line_end_overshoot = 0.15 * layout.position_map.line_height;
819 let whitespace_setting = self
820 .editor
821 .read(cx)
822 .buffer
823 .read(cx)
824 .settings_at(0, cx)
825 .show_whitespaces;
826
827 cx.with_content_mask(
828 Some(ContentMask {
829 bounds: text_bounds,
830 }),
831 |cx| {
832 // todo!("cursor region")
833 // cx.scene().push_cursor_region(CursorRegion {
834 // bounds,
835 // style: if !editor.link_go_to_definition_state.definitions.is_empty {
836 // CursorStyle::PointingHand
837 // } else {
838 // CursorStyle::IBeam
839 // },
840 // });
841
842 let fold_corner_radius = 0.15 * layout.position_map.line_height;
843 cx.with_element_id(Some("folds"), |cx| {
844 let snapshot = &layout.position_map.snapshot;
845 for fold in snapshot.folds_in_range(layout.visible_anchor_range.clone()) {
846 let fold_range = fold.range.clone();
847 let display_range = fold.range.start.to_display_point(&snapshot)
848 ..fold.range.end.to_display_point(&snapshot);
849 debug_assert_eq!(display_range.start.row(), display_range.end.row());
850 let row = display_range.start.row();
851
852 let line_layout = &layout.position_map.line_layouts
853 [(row - layout.visible_display_row_range.start) as usize]
854 .line;
855 let start_x = content_origin.x
856 + line_layout.x_for_index(display_range.start.column() as usize)
857 - layout.position_map.scroll_position.x;
858 let start_y = content_origin.y
859 + row as f32 * layout.position_map.line_height
860 - layout.position_map.scroll_position.y;
861 let end_x = content_origin.x
862 + line_layout.x_for_index(display_range.end.column() as usize)
863 - layout.position_map.scroll_position.x;
864
865 let fold_bounds = Bounds {
866 origin: point(start_x, start_y),
867 size: size(end_x - start_x, layout.position_map.line_height),
868 };
869
870 let fold_background = cx.with_z_index(1, |cx| {
871 div()
872 .id(fold.id)
873 .size_full()
874 .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
875 .on_click(cx.listener_for(
876 &self.editor,
877 move |editor: &mut Editor, _, cx| {
878 editor.unfold_ranges(
879 [fold_range.start..fold_range.end],
880 true,
881 false,
882 cx,
883 );
884 cx.stop_propagation();
885 },
886 ))
887 .draw(
888 fold_bounds.origin,
889 fold_bounds.size,
890 cx,
891 |fold_element_state, cx| {
892 if fold_element_state.is_active() {
893 gpui::blue()
894 } else if fold_bounds.contains_point(&cx.mouse_position()) {
895 gpui::black()
896 } else {
897 gpui::red()
898 }
899 },
900 )
901 });
902
903 self.paint_highlighted_range(
904 display_range.clone(),
905 fold_background,
906 fold_corner_radius,
907 fold_corner_radius * 2.,
908 layout,
909 content_origin,
910 text_bounds,
911 cx,
912 );
913 }
914 });
915
916 for (range, color) in &layout.highlighted_ranges {
917 self.paint_highlighted_range(
918 range.clone(),
919 *color,
920 Pixels::ZERO,
921 line_end_overshoot,
922 layout,
923 content_origin,
924 text_bounds,
925 cx,
926 );
927 }
928
929 let mut cursors = SmallVec::<[Cursor; 32]>::new();
930 let corner_radius = 0.15 * layout.position_map.line_height;
931 let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
932
933 for (selection_style, selections) in &layout.selections {
934 for selection in selections {
935 self.paint_highlighted_range(
936 selection.range.clone(),
937 selection_style.selection,
938 corner_radius,
939 corner_radius * 2.,
940 layout,
941 content_origin,
942 text_bounds,
943 cx,
944 );
945
946 if selection.is_local && !selection.range.is_empty() {
947 invisible_display_ranges.push(selection.range.clone());
948 }
949
950 if !selection.is_local || self.editor.read(cx).show_local_cursors(cx) {
951 let cursor_position = selection.head;
952 if layout
953 .visible_display_row_range
954 .contains(&cursor_position.row())
955 {
956 let cursor_row_layout = &layout.position_map.line_layouts
957 [(cursor_position.row() - start_row) as usize]
958 .line;
959 let cursor_column = cursor_position.column() as usize;
960
961 let cursor_character_x =
962 cursor_row_layout.x_for_index(cursor_column);
963 let mut block_width = cursor_row_layout
964 .x_for_index(cursor_column + 1)
965 - cursor_character_x;
966 if block_width == Pixels::ZERO {
967 block_width = layout.position_map.em_width;
968 }
969 let block_text = if let CursorShape::Block = selection.cursor_shape
970 {
971 layout
972 .position_map
973 .snapshot
974 .chars_at(cursor_position)
975 .next()
976 .and_then(|(character, _)| {
977 let text = SharedString::from(character.to_string());
978 let len = text.len();
979 cx.text_system()
980 .shape_line(
981 text,
982 cursor_row_layout.font_size,
983 &[TextRun {
984 len,
985 font: self.style.text.font(),
986 color: self.style.background,
987 background_color: None,
988 underline: None,
989 }],
990 )
991 .log_err()
992 })
993 } else {
994 None
995 };
996
997 let x = cursor_character_x - layout.position_map.scroll_position.x;
998 let y = cursor_position.row() as f32
999 * layout.position_map.line_height
1000 - layout.position_map.scroll_position.y;
1001 if selection.is_newest {
1002 self.editor.update(cx, |editor, _| {
1003 editor.pixel_position_of_newest_cursor = Some(point(
1004 text_bounds.origin.x + x + block_width / 2.,
1005 text_bounds.origin.y
1006 + y
1007 + layout.position_map.line_height / 2.,
1008 ))
1009 });
1010 }
1011 cursors.push(Cursor {
1012 color: selection_style.cursor,
1013 block_width,
1014 origin: point(x, y),
1015 line_height: layout.position_map.line_height,
1016 shape: selection.cursor_shape,
1017 block_text,
1018 });
1019 }
1020 }
1021 }
1022 }
1023
1024 for (ix, line_with_invisibles) in
1025 layout.position_map.line_layouts.iter().enumerate()
1026 {
1027 let row = start_row + ix as u32;
1028 line_with_invisibles.draw(
1029 layout,
1030 row,
1031 content_origin,
1032 whitespace_setting,
1033 &invisible_display_ranges,
1034 cx,
1035 )
1036 }
1037
1038 cx.with_z_index(0, |cx| {
1039 for cursor in cursors {
1040 cursor.paint(content_origin, cx);
1041 }
1042 });
1043
1044 cx.with_z_index(1, |cx| {
1045 if let Some((position, mut context_menu)) = layout.context_menu.take() {
1046 let available_space =
1047 size(AvailableSpace::MinContent, AvailableSpace::MinContent);
1048 let context_menu_size = context_menu.measure(available_space, cx);
1049
1050 let cursor_row_layout = &layout.position_map.line_layouts
1051 [(position.row() - start_row) as usize]
1052 .line;
1053 let x = cursor_row_layout.x_for_index(position.column() as usize)
1054 - layout.position_map.scroll_position.x;
1055 let y = (position.row() + 1) as f32 * layout.position_map.line_height
1056 - layout.position_map.scroll_position.y;
1057 let mut list_origin = content_origin + point(x, y);
1058 let list_width = context_menu_size.width;
1059 let list_height = context_menu_size.height;
1060
1061 // Snap the right edge of the list to the right edge of the window if
1062 // its horizontal bounds overflow.
1063 if list_origin.x + list_width > cx.viewport_size().width {
1064 list_origin.x =
1065 (cx.viewport_size().width - list_width).max(Pixels::ZERO);
1066 }
1067
1068 if list_origin.y + list_height > text_bounds.lower_right().y {
1069 list_origin.y -= layout.position_map.line_height + list_height;
1070 }
1071
1072 cx.break_content_mask(|cx| {
1073 context_menu.draw(list_origin, available_space, cx)
1074 });
1075 }
1076
1077 if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() {
1078 let available_space =
1079 size(AvailableSpace::MinContent, AvailableSpace::MinContent);
1080
1081 // This is safe because we check on layout whether the required row is available
1082 let hovered_row_layout = &layout.position_map.line_layouts
1083 [(position.row() - start_row) as usize]
1084 .line;
1085
1086 // Minimum required size: Take the first popover, and add 1.5 times the minimum popover
1087 // height. This is the size we will use to decide whether to render popovers above or below
1088 // the hovered line.
1089 let first_size = hover_popovers[0].measure(available_space, cx);
1090 let height_to_reserve = first_size.height
1091 + 1.5 * MIN_POPOVER_LINE_HEIGHT * layout.position_map.line_height;
1092
1093 // Compute Hovered Point
1094 let x = hovered_row_layout.x_for_index(position.column() as usize)
1095 - layout.position_map.scroll_position.x;
1096 let y = position.row() as f32 * layout.position_map.line_height
1097 - layout.position_map.scroll_position.y;
1098 let hovered_point = content_origin + point(x, y);
1099
1100 if hovered_point.y - height_to_reserve > Pixels::ZERO {
1101 // There is enough space above. Render popovers above the hovered point
1102 let mut current_y = hovered_point.y;
1103 for mut hover_popover in hover_popovers {
1104 let size = hover_popover.measure(available_space, cx);
1105 let mut popover_origin =
1106 point(hovered_point.x, current_y - size.height);
1107
1108 let x_out_of_bounds =
1109 text_bounds.upper_right().x - (popover_origin.x + size.width);
1110 if x_out_of_bounds < Pixels::ZERO {
1111 popover_origin.x = popover_origin.x + x_out_of_bounds;
1112 }
1113
1114 cx.break_content_mask(|cx| {
1115 hover_popover.draw(popover_origin, available_space, cx)
1116 });
1117
1118 current_y = popover_origin.y - HOVER_POPOVER_GAP;
1119 }
1120 } else {
1121 // There is not enough space above. Render popovers below the hovered point
1122 let mut current_y = hovered_point.y + layout.position_map.line_height;
1123 for mut hover_popover in hover_popovers {
1124 let size = hover_popover.measure(available_space, cx);
1125 let mut popover_origin = point(hovered_point.x, current_y);
1126
1127 let x_out_of_bounds =
1128 text_bounds.upper_right().x - (popover_origin.x + size.width);
1129 if x_out_of_bounds < Pixels::ZERO {
1130 popover_origin.x = popover_origin.x + x_out_of_bounds;
1131 }
1132
1133 hover_popover.draw(popover_origin, available_space, cx);
1134
1135 current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
1136 }
1137 }
1138 }
1139 })
1140 },
1141 )
1142 }
1143
1144 fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
1145 bounds.upper_right().x - self.style.scrollbar_width
1146 }
1147
1148 // fn paint_scrollbar(
1149 // &mut self,
1150 // bounds: Bounds<Pixels>,
1151 // layout: &mut LayoutState,
1152 // editor: &Editor,
1153 // cx: &mut ViewContext<Editor>,
1154 // ) {
1155 // enum ScrollbarMouseHandlers {}
1156 // if layout.mode != EditorMode::Full {
1157 // return;
1158 // }
1159
1160 // let style = &self.style.theme.scrollbar;
1161
1162 // let top = bounds.min_y;
1163 // let bottom = bounds.max_y;
1164 // let right = bounds.max_x;
1165 // let left = self.scrollbar_left(&bounds);
1166 // let row_range = &layout.scrollbar_row_range;
1167 // let max_row = layout.max_row as f32 + (row_range.end - row_range.start);
1168
1169 // let mut height = bounds.height();
1170 // let mut first_row_y_offset = 0.0;
1171
1172 // // Impose a minimum height on the scrollbar thumb
1173 // let row_height = height / max_row;
1174 // let min_thumb_height =
1175 // style.min_height_factor * cx.font_cache.line_height(self.style.text.font_size);
1176 // let thumb_height = (row_range.end - row_range.start) * row_height;
1177 // if thumb_height < min_thumb_height {
1178 // first_row_y_offset = (min_thumb_height - thumb_height) / 2.0;
1179 // height -= min_thumb_height - thumb_height;
1180 // }
1181
1182 // let y_for_row = |row: f32| -> f32 { top + first_row_y_offset + row * row_height };
1183
1184 // let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
1185 // let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
1186 // let track_bounds = Bounds::<Pixels>::from_points(point(left, top), point(right, bottom));
1187 // let thumb_bounds = Bounds::<Pixels>::from_points(point(left, thumb_top), point(right, thumb_bottom));
1188
1189 // if layout.show_scrollbars {
1190 // cx.paint_quad(Quad {
1191 // bounds: track_bounds,
1192 // border: style.track.border.into(),
1193 // background: style.track.background_color,
1194 // ..Default::default()
1195 // });
1196 // let scrollbar_settings = settings::get::<EditorSettings>(cx).scrollbar;
1197 // let theme = theme::current(cx);
1198 // let scrollbar_theme = &theme.editor.scrollbar;
1199 // if layout.is_singleton && scrollbar_settings.selections {
1200 // let start_anchor = Anchor::min();
1201 // let end_anchor = Anchor::max;
1202 // let color = scrollbar_theme.selections;
1203 // let border = Border {
1204 // width: 1.,
1205 // color: style.thumb.border.color,
1206 // overlay: false,
1207 // top: false,
1208 // right: true,
1209 // bottom: false,
1210 // left: true,
1211 // };
1212 // let mut push_region = |start: DisplayPoint, end: DisplayPoint| {
1213 // let start_y = y_for_row(start.row() as f32);
1214 // let mut end_y = y_for_row(end.row() as f32);
1215 // if end_y - start_y < 1. {
1216 // end_y = start_y + 1.;
1217 // }
1218 // let bounds = Bounds::<Pixels>::from_points(point(left, start_y), point(right, end_y));
1219
1220 // cx.paint_quad(Quad {
1221 // bounds,
1222 // background: Some(color),
1223 // border: border.into(),
1224 // corner_radii: style.thumb.corner_radii.into(),
1225 // })
1226 // };
1227 // let background_ranges = editor
1228 // .background_highlight_row_ranges::<crate::items::BufferSearchHighlights>(
1229 // start_anchor..end_anchor,
1230 // &layout.position_map.snapshot,
1231 // 50000,
1232 // );
1233 // for row in background_ranges {
1234 // let start = row.start();
1235 // let end = row.end();
1236 // push_region(*start, *end);
1237 // }
1238 // }
1239
1240 // if layout.is_singleton && scrollbar_settings.git_diff {
1241 // let diff_style = scrollbar_theme.git.clone();
1242 // for hunk in layout
1243 // .position_map
1244 // .snapshot
1245 // .buffer_snapshot
1246 // .git_diff_hunks_in_range(0..(max_row.floor() as u32))
1247 // {
1248 // let start_display = Point::new(hunk.buffer_range.start, 0)
1249 // .to_display_point(&layout.position_map.snapshot.display_snapshot);
1250 // let end_display = Point::new(hunk.buffer_range.end, 0)
1251 // .to_display_point(&layout.position_map.snapshot.display_snapshot);
1252 // let start_y = y_for_row(start_display.row() as f32);
1253 // let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
1254 // y_for_row((end_display.row() + 1) as f32)
1255 // } else {
1256 // y_for_row((end_display.row()) as f32)
1257 // };
1258
1259 // if end_y - start_y < 1. {
1260 // end_y = start_y + 1.;
1261 // }
1262 // let bounds = Bounds::<Pixels>::from_points(point(left, start_y), point(right, end_y));
1263
1264 // let color = match hunk.status() {
1265 // DiffHunkStatus::Added => diff_style.inserted,
1266 // DiffHunkStatus::Modified => diff_style.modified,
1267 // DiffHunkStatus::Removed => diff_style.deleted,
1268 // };
1269
1270 // let border = Border {
1271 // width: 1.,
1272 // color: style.thumb.border.color,
1273 // overlay: false,
1274 // top: false,
1275 // right: true,
1276 // bottom: false,
1277 // left: true,
1278 // };
1279
1280 // cx.paint_quad(Quad {
1281 // bounds,
1282 // background: Some(color),
1283 // border: border.into(),
1284 // corner_radii: style.thumb.corner_radii.into(),
1285 // })
1286 // }
1287 // }
1288
1289 // cx.paint_quad(Quad {
1290 // bounds: thumb_bounds,
1291 // border: style.thumb.border.into(),
1292 // background: style.thumb.background_color,
1293 // corner_radii: style.thumb.corner_radii.into(),
1294 // });
1295 // }
1296
1297 // cx.scene().push_cursor_region(CursorRegion {
1298 // bounds: track_bounds,
1299 // style: CursorStyle::Arrow,
1300 // });
1301 // let region_id = cx.view_id();
1302 // cx.scene().push_mouse_region(
1303 // MouseRegion::new::<ScrollbarMouseHandlers>(region_id, region_id, track_bounds)
1304 // .on_move(move |event, editor: &mut Editor, cx| {
1305 // if event.pressed_button.is_none() {
1306 // editor.scroll_manager.show_scrollbar(cx);
1307 // }
1308 // })
1309 // .on_down(MouseButton::Left, {
1310 // let row_range = row_range.clone();
1311 // move |event, editor: &mut Editor, cx| {
1312 // let y = event.position.y;
1313 // if y < thumb_top || thumb_bottom < y {
1314 // let center_row = ((y - top) * max_row as f32 / height).round() as u32;
1315 // let top_row = center_row
1316 // .saturating_sub((row_range.end - row_range.start) as u32 / 2);
1317 // let mut position = editor.scroll_position(cx);
1318 // position.set_y(top_row as f32);
1319 // editor.set_scroll_position(position, cx);
1320 // } else {
1321 // editor.scroll_manager.show_scrollbar(cx);
1322 // }
1323 // }
1324 // })
1325 // .on_drag(MouseButton::Left, {
1326 // move |event, editor: &mut Editor, cx| {
1327 // if event.end {
1328 // return;
1329 // }
1330
1331 // let y = event.prev_mouse_position.y;
1332 // let new_y = event.position.y;
1333 // if thumb_top < y && y < thumb_bottom {
1334 // let mut position = editor.scroll_position(cx);
1335 // position.set_y(position.y + (new_y - y) * (max_row as f32) / height);
1336 // if position.y < 0.0 {
1337 // position.set_y(0.);
1338 // }
1339 // editor.set_scroll_position(position, cx);
1340 // }
1341 // }
1342 // }),
1343 // );
1344 // }
1345
1346 #[allow(clippy::too_many_arguments)]
1347 fn paint_highlighted_range(
1348 &self,
1349 range: Range<DisplayPoint>,
1350 color: Hsla,
1351 corner_radius: Pixels,
1352 line_end_overshoot: Pixels,
1353 layout: &LayoutState,
1354 content_origin: gpui::Point<Pixels>,
1355 bounds: Bounds<Pixels>,
1356 cx: &mut WindowContext,
1357 ) {
1358 let start_row = layout.visible_display_row_range.start;
1359 let end_row = layout.visible_display_row_range.end;
1360 if range.start != range.end {
1361 let row_range = if range.end.column() == 0 {
1362 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
1363 } else {
1364 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
1365 };
1366
1367 let highlighted_range = HighlightedRange {
1368 color,
1369 line_height: layout.position_map.line_height,
1370 corner_radius,
1371 start_y: content_origin.y
1372 + row_range.start as f32 * layout.position_map.line_height
1373 - layout.position_map.scroll_position.y,
1374 lines: row_range
1375 .into_iter()
1376 .map(|row| {
1377 let line_layout =
1378 &layout.position_map.line_layouts[(row - start_row) as usize].line;
1379 HighlightedRangeLine {
1380 start_x: if row == range.start.row() {
1381 content_origin.x
1382 + line_layout.x_for_index(range.start.column() as usize)
1383 - layout.position_map.scroll_position.x
1384 } else {
1385 content_origin.x - layout.position_map.scroll_position.x
1386 },
1387 end_x: if row == range.end.row() {
1388 content_origin.x
1389 + line_layout.x_for_index(range.end.column() as usize)
1390 - layout.position_map.scroll_position.x
1391 } else {
1392 content_origin.x + line_layout.width + line_end_overshoot
1393 - layout.position_map.scroll_position.x
1394 },
1395 }
1396 })
1397 .collect(),
1398 };
1399
1400 highlighted_range.paint(bounds, cx);
1401 }
1402 }
1403
1404 fn paint_blocks(
1405 &mut self,
1406 bounds: Bounds<Pixels>,
1407 layout: &mut LayoutState,
1408 cx: &mut WindowContext,
1409 ) {
1410 let scroll_position = layout.position_map.snapshot.scroll_position();
1411 let scroll_left = scroll_position.x * layout.position_map.em_width;
1412 let scroll_top = scroll_position.y * layout.position_map.line_height;
1413
1414 for block in layout.blocks.drain(..) {
1415 let mut origin = bounds.origin
1416 + point(
1417 Pixels::ZERO,
1418 block.row as f32 * layout.position_map.line_height - scroll_top,
1419 );
1420 if !matches!(block.style, BlockStyle::Sticky) {
1421 origin += point(-scroll_left, Pixels::ZERO);
1422 }
1423 block.element.draw(origin, block.available_space, cx);
1424 }
1425 }
1426
1427 fn column_pixels(&self, column: usize, cx: &WindowContext) -> Pixels {
1428 let style = &self.style;
1429 let font_size = style.text.font_size.to_pixels(cx.rem_size());
1430 let layout = cx
1431 .text_system()
1432 .shape_line(
1433 SharedString::from(" ".repeat(column)),
1434 font_size,
1435 &[TextRun {
1436 len: column,
1437 font: style.text.font(),
1438 color: Hsla::default(),
1439 background_color: None,
1440 underline: None,
1441 }],
1442 )
1443 .unwrap();
1444
1445 layout.width
1446 }
1447
1448 fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &WindowContext) -> Pixels {
1449 let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
1450 self.column_pixels(digit_count, cx)
1451 }
1452
1453 //Folds contained in a hunk are ignored apart from shrinking visual size
1454 //If a fold contains any hunks then that fold line is marked as modified
1455 fn layout_git_gutters(
1456 &self,
1457 display_rows: Range<u32>,
1458 snapshot: &EditorSnapshot,
1459 ) -> Vec<DisplayDiffHunk> {
1460 let buffer_snapshot = &snapshot.buffer_snapshot;
1461
1462 let buffer_start_row = DisplayPoint::new(display_rows.start, 0)
1463 .to_point(snapshot)
1464 .row;
1465 let buffer_end_row = DisplayPoint::new(display_rows.end, 0)
1466 .to_point(snapshot)
1467 .row;
1468
1469 buffer_snapshot
1470 .git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
1471 .map(|hunk| diff_hunk_to_display(hunk, snapshot))
1472 .dedup()
1473 .collect()
1474 }
1475
1476 fn calculate_relative_line_numbers(
1477 &self,
1478 snapshot: &EditorSnapshot,
1479 rows: &Range<u32>,
1480 relative_to: Option<u32>,
1481 ) -> HashMap<u32, u32> {
1482 let mut relative_rows: HashMap<u32, u32> = Default::default();
1483 let Some(relative_to) = relative_to else {
1484 return relative_rows;
1485 };
1486
1487 let start = rows.start.min(relative_to);
1488 let end = rows.end.max(relative_to);
1489
1490 let buffer_rows = snapshot
1491 .buffer_rows(start)
1492 .take(1 + (end - start) as usize)
1493 .collect::<Vec<_>>();
1494
1495 let head_idx = relative_to - start;
1496 let mut delta = 1;
1497 let mut i = head_idx + 1;
1498 while i < buffer_rows.len() as u32 {
1499 if buffer_rows[i as usize].is_some() {
1500 if rows.contains(&(i + start)) {
1501 relative_rows.insert(i + start, delta);
1502 }
1503 delta += 1;
1504 }
1505 i += 1;
1506 }
1507 delta = 1;
1508 i = head_idx.min(buffer_rows.len() as u32 - 1);
1509 while i > 0 && buffer_rows[i as usize].is_none() {
1510 i -= 1;
1511 }
1512
1513 while i > 0 {
1514 i -= 1;
1515 if buffer_rows[i as usize].is_some() {
1516 if rows.contains(&(i + start)) {
1517 relative_rows.insert(i + start, delta);
1518 }
1519 delta += 1;
1520 }
1521 }
1522
1523 relative_rows
1524 }
1525
1526 fn shape_line_numbers(
1527 &self,
1528 rows: Range<u32>,
1529 active_rows: &BTreeMap<u32, bool>,
1530 newest_selection_head: DisplayPoint,
1531 is_singleton: bool,
1532 snapshot: &EditorSnapshot,
1533 cx: &ViewContext<Editor>,
1534 ) -> (
1535 Vec<Option<ShapedLine>>,
1536 Vec<Option<(FoldStatus, BufferRow, bool)>>,
1537 ) {
1538 let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
1539 let include_line_numbers = snapshot.mode == EditorMode::Full;
1540 let mut shaped_line_numbers = Vec::with_capacity(rows.len());
1541 let mut fold_statuses = Vec::with_capacity(rows.len());
1542 let mut line_number = String::new();
1543 let is_relative = EditorSettings::get_global(cx).relative_line_numbers;
1544 let relative_to = if is_relative {
1545 Some(newest_selection_head.row())
1546 } else {
1547 None
1548 };
1549
1550 let relative_rows = self.calculate_relative_line_numbers(&snapshot, &rows, relative_to);
1551
1552 for (ix, row) in snapshot
1553 .buffer_rows(rows.start)
1554 .take((rows.end - rows.start) as usize)
1555 .enumerate()
1556 {
1557 let display_row = rows.start + ix as u32;
1558 let (active, color) = if active_rows.contains_key(&display_row) {
1559 (true, cx.theme().colors().editor_active_line_number)
1560 } else {
1561 (false, cx.theme().colors().editor_line_number)
1562 };
1563 if let Some(buffer_row) = row {
1564 if include_line_numbers {
1565 line_number.clear();
1566 let default_number = buffer_row + 1;
1567 let number = relative_rows
1568 .get(&(ix as u32 + rows.start))
1569 .unwrap_or(&default_number);
1570 write!(&mut line_number, "{}", number).unwrap();
1571 let run = TextRun {
1572 len: line_number.len(),
1573 font: self.style.text.font(),
1574 color,
1575 background_color: None,
1576 underline: None,
1577 };
1578 let shaped_line = cx
1579 .text_system()
1580 .shape_line(line_number.clone().into(), font_size, &[run])
1581 .unwrap();
1582 shaped_line_numbers.push(Some(shaped_line));
1583 fold_statuses.push(
1584 is_singleton
1585 .then(|| {
1586 snapshot
1587 .fold_for_line(buffer_row)
1588 .map(|fold_status| (fold_status, buffer_row, active))
1589 })
1590 .flatten(),
1591 )
1592 }
1593 } else {
1594 fold_statuses.push(None);
1595 shaped_line_numbers.push(None);
1596 }
1597 }
1598
1599 (shaped_line_numbers, fold_statuses)
1600 }
1601
1602 fn layout_lines(
1603 &self,
1604 rows: Range<u32>,
1605 line_number_layouts: &[Option<ShapedLine>],
1606 snapshot: &EditorSnapshot,
1607 cx: &ViewContext<Editor>,
1608 ) -> Vec<LineWithInvisibles> {
1609 if rows.start >= rows.end {
1610 return Vec::new();
1611 }
1612
1613 // When the editor is empty and unfocused, then show the placeholder.
1614 if snapshot.is_empty() {
1615 let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
1616 let placeholder_color = cx.theme().styles.colors.text_placeholder;
1617 let placeholder_text = snapshot.placeholder_text();
1618 let placeholder_lines = placeholder_text
1619 .as_ref()
1620 .map_or("", AsRef::as_ref)
1621 .split('\n')
1622 .skip(rows.start as usize)
1623 .chain(iter::repeat(""))
1624 .take(rows.len());
1625 placeholder_lines
1626 .filter_map(move |line| {
1627 let run = TextRun {
1628 len: line.len(),
1629 font: self.style.text.font(),
1630 color: placeholder_color,
1631 background_color: None,
1632 underline: Default::default(),
1633 };
1634 cx.text_system()
1635 .shape_line(line.to_string().into(), font_size, &[run])
1636 .log_err()
1637 })
1638 .map(|line| LineWithInvisibles {
1639 line,
1640 invisibles: Vec::new(),
1641 })
1642 .collect()
1643 } else {
1644 let chunks = snapshot.highlighted_chunks(rows.clone(), true, &self.style);
1645 LineWithInvisibles::from_chunks(
1646 chunks,
1647 &self.style.text,
1648 MAX_LINE_LEN,
1649 rows.len() as usize,
1650 line_number_layouts,
1651 snapshot.mode,
1652 cx,
1653 )
1654 }
1655 }
1656
1657 fn compute_layout(
1658 &mut self,
1659 mut bounds: Bounds<Pixels>,
1660 cx: &mut WindowContext,
1661 ) -> LayoutState {
1662 self.editor.update(cx, |editor, cx| {
1663 let snapshot = editor.snapshot(cx);
1664 let style = self.style.clone();
1665
1666 let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
1667 let font_size = style.text.font_size.to_pixels(cx.rem_size());
1668 let line_height = style.text.line_height_in_pixels(cx.rem_size());
1669 let em_width = cx
1670 .text_system()
1671 .typographic_bounds(font_id, font_size, 'm')
1672 .unwrap()
1673 .size
1674 .width;
1675 let em_advance = cx
1676 .text_system()
1677 .advance(font_id, font_size, 'm')
1678 .unwrap()
1679 .width;
1680
1681 let gutter_padding;
1682 let gutter_width;
1683 let gutter_margin;
1684 if snapshot.show_gutter {
1685 let descent = cx.text_system().descent(font_id, font_size).unwrap();
1686
1687 let gutter_padding_factor = 3.5;
1688 gutter_padding = (em_width * gutter_padding_factor).round();
1689 gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
1690 gutter_margin = -descent;
1691 } else {
1692 gutter_padding = Pixels::ZERO;
1693 gutter_width = Pixels::ZERO;
1694 gutter_margin = Pixels::ZERO;
1695 };
1696
1697 editor.gutter_width = gutter_width;
1698
1699 let text_width = bounds.size.width - gutter_width;
1700 let overscroll = size(em_width, px(0.));
1701 let snapshot = {
1702 editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
1703
1704 let editor_width = text_width - gutter_margin - overscroll.width - em_width;
1705 let wrap_width = match editor.soft_wrap_mode(cx) {
1706 SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
1707 SoftWrap::EditorWidth => editor_width,
1708 SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
1709 };
1710
1711 if editor.set_wrap_width(Some(wrap_width), cx) {
1712 editor.snapshot(cx)
1713 } else {
1714 snapshot
1715 }
1716 };
1717
1718 let wrap_guides = editor
1719 .wrap_guides(cx)
1720 .iter()
1721 .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
1722 .collect::<SmallVec<[_; 2]>>();
1723
1724 let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
1725 let gutter_size = size(gutter_width, bounds.size.height);
1726 let text_size = size(text_width, bounds.size.height);
1727
1728 let autoscroll_horizontally =
1729 editor.autoscroll_vertically(bounds.size.height, line_height, cx);
1730 let mut snapshot = editor.snapshot(cx);
1731
1732 let scroll_position = snapshot.scroll_position();
1733 // The scroll position is a fractional point, the whole number of which represents
1734 // the top of the window in terms of display rows.
1735 let start_row = scroll_position.y as u32;
1736 let height_in_lines = f32::from(bounds.size.height / line_height);
1737 let max_row = snapshot.max_point().row();
1738
1739 // Add 1 to ensure selections bleed off screen
1740 let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row);
1741
1742 let start_anchor = if start_row == 0 {
1743 Anchor::min()
1744 } else {
1745 snapshot
1746 .buffer_snapshot
1747 .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
1748 };
1749 let end_anchor = if end_row > max_row {
1750 Anchor::max()
1751 } else {
1752 snapshot
1753 .buffer_snapshot
1754 .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
1755 };
1756
1757 let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
1758 let mut active_rows = BTreeMap::new();
1759 let is_singleton = editor.is_singleton(cx);
1760
1761 let highlighted_rows = editor.highlighted_rows();
1762 let highlighted_ranges = editor.background_highlights_in_range(
1763 start_anchor..end_anchor,
1764 &snapshot.display_snapshot,
1765 cx.theme().colors(),
1766 );
1767
1768 let mut newest_selection_head = None;
1769
1770 if editor.show_local_selections {
1771 let mut local_selections: Vec<Selection<Point>> = editor
1772 .selections
1773 .disjoint_in_range(start_anchor..end_anchor, cx);
1774 local_selections.extend(editor.selections.pending(cx));
1775 let mut layouts = Vec::new();
1776 let newest = editor.selections.newest(cx);
1777 for selection in local_selections.drain(..) {
1778 let is_empty = selection.start == selection.end;
1779 let is_newest = selection == newest;
1780
1781 let layout = SelectionLayout::new(
1782 selection,
1783 editor.selections.line_mode,
1784 editor.cursor_shape,
1785 &snapshot.display_snapshot,
1786 is_newest,
1787 true,
1788 );
1789 if is_newest {
1790 newest_selection_head = Some(layout.head);
1791 }
1792
1793 for row in cmp::max(layout.active_rows.start, start_row)
1794 ..=cmp::min(layout.active_rows.end, end_row)
1795 {
1796 let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
1797 *contains_non_empty_selection |= !is_empty;
1798 }
1799 layouts.push(layout);
1800 }
1801
1802 selections.push((style.local_player, layouts));
1803 }
1804
1805 if let Some(collaboration_hub) = &editor.collaboration_hub {
1806 // When following someone, render the local selections in their color.
1807 if let Some(leader_id) = editor.leader_peer_id {
1808 if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
1809 if let Some(participant_index) = collaboration_hub
1810 .user_participant_indices(cx)
1811 .get(&collaborator.user_id)
1812 {
1813 if let Some((local_selection_style, _)) = selections.first_mut() {
1814 *local_selection_style = cx
1815 .theme()
1816 .players()
1817 .color_for_participant(participant_index.0);
1818 }
1819 }
1820 }
1821 }
1822
1823 let mut remote_selections = HashMap::default();
1824 for selection in snapshot.remote_selections_in_range(
1825 &(start_anchor..end_anchor),
1826 collaboration_hub.as_ref(),
1827 cx,
1828 ) {
1829 let selection_style = if let Some(participant_index) = selection.participant_index {
1830 cx.theme()
1831 .players()
1832 .color_for_participant(participant_index.0)
1833 } else {
1834 cx.theme().players().absent()
1835 };
1836
1837 // Don't re-render the leader's selections, since the local selections
1838 // match theirs.
1839 if Some(selection.peer_id) == editor.leader_peer_id {
1840 continue;
1841 }
1842
1843 remote_selections
1844 .entry(selection.replica_id)
1845 .or_insert((selection_style, Vec::new()))
1846 .1
1847 .push(SelectionLayout::new(
1848 selection.selection,
1849 selection.line_mode,
1850 selection.cursor_shape,
1851 &snapshot.display_snapshot,
1852 false,
1853 false,
1854 ));
1855 }
1856
1857 selections.extend(remote_selections.into_values());
1858 }
1859
1860 let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
1861 let show_scrollbars = match scrollbar_settings.show {
1862 ShowScrollbar::Auto => {
1863 // Git
1864 (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
1865 ||
1866 // Selections
1867 (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty())
1868 // Scrollmanager
1869 || editor.scroll_manager.scrollbars_visible()
1870 }
1871 ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
1872 ShowScrollbar::Always => true,
1873 ShowScrollbar::Never => false,
1874 };
1875
1876 let head_for_relative = newest_selection_head.unwrap_or_else(|| {
1877 let newest = editor.selections.newest::<Point>(cx);
1878 SelectionLayout::new(
1879 newest,
1880 editor.selections.line_mode,
1881 editor.cursor_shape,
1882 &snapshot.display_snapshot,
1883 true,
1884 true,
1885 )
1886 .head
1887 });
1888
1889 let (line_numbers, fold_statuses) = self.shape_line_numbers(
1890 start_row..end_row,
1891 &active_rows,
1892 head_for_relative,
1893 is_singleton,
1894 &snapshot,
1895 cx,
1896 );
1897
1898 let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
1899
1900 let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
1901
1902 let mut max_visible_line_width = Pixels::ZERO;
1903 let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
1904 for line_with_invisibles in &line_layouts {
1905 if line_with_invisibles.line.width > max_visible_line_width {
1906 max_visible_line_width = line_with_invisibles.line.width;
1907 }
1908 }
1909
1910 let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
1911 .unwrap()
1912 .width;
1913 let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
1914
1915 let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| {
1916 self.layout_blocks(
1917 start_row..end_row,
1918 &snapshot,
1919 bounds.size.width,
1920 scroll_width,
1921 gutter_padding,
1922 gutter_width,
1923 em_width,
1924 gutter_width + gutter_margin,
1925 line_height,
1926 &style,
1927 &line_layouts,
1928 editor,
1929 cx,
1930 )
1931 });
1932
1933 let scroll_max = point(
1934 f32::from((scroll_width - text_size.width) / em_width).max(0.0),
1935 max_row as f32,
1936 );
1937
1938 let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
1939
1940 let autoscrolled = if autoscroll_horizontally {
1941 editor.autoscroll_horizontally(
1942 start_row,
1943 text_size.width,
1944 scroll_width,
1945 em_width,
1946 &line_layouts,
1947 cx,
1948 )
1949 } else {
1950 false
1951 };
1952
1953 if clamped || autoscrolled {
1954 snapshot = editor.snapshot(cx);
1955 }
1956
1957 let mut context_menu = None;
1958 let mut code_actions_indicator = None;
1959 if let Some(newest_selection_head) = newest_selection_head {
1960 if (start_row..end_row).contains(&newest_selection_head.row()) {
1961 if editor.context_menu_visible() {
1962 let max_height = (12. * line_height).min((bounds.size.height - line_height) / 2.);
1963 context_menu =
1964 editor.render_context_menu(newest_selection_head, &self.style, max_height, cx);
1965 }
1966
1967 let active = matches!(
1968 editor.context_menu.read().as_ref(),
1969 Some(crate::ContextMenu::CodeActions(_))
1970 );
1971
1972 code_actions_indicator = editor
1973 .render_code_actions_indicator(&style, active, cx)
1974 .map(|element| CodeActionsIndicator {
1975 row: newest_selection_head.row(),
1976 button: element,
1977 });
1978 }
1979 }
1980
1981 let visible_rows = start_row..start_row + line_layouts.len() as u32;
1982 let max_size = size(
1983 (120. * em_width) // Default size
1984 .min(bounds.size.width / 2.) // Shrink to half of the editor width
1985 .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
1986 (16. * line_height) // Default size
1987 .min(bounds.size.height / 2.) // Shrink to half of the editor height
1988 .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
1989 );
1990
1991 let mut hover = editor.hover_state.render(
1992 &snapshot,
1993 &style,
1994 visible_rows,
1995 max_size,
1996 editor.workspace.as_ref().map(|(w, _)| w.clone()),
1997 cx,
1998 );
1999
2000 let mut fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| {
2001 editor.render_fold_indicators(
2002 fold_statuses,
2003 &style,
2004 editor.gutter_hovered,
2005 line_height,
2006 gutter_margin,
2007 cx,
2008 )
2009 });
2010
2011 let invisible_symbol_font_size = font_size / 2.;
2012 let tab_invisible = cx
2013 .text_system()
2014 .shape_line(
2015 "→".into(),
2016 invisible_symbol_font_size,
2017 &[TextRun {
2018 len: "→".len(),
2019 font: self.style.text.font(),
2020 color: cx.theme().colors().editor_invisible,
2021 background_color: None,
2022 underline: None,
2023 }],
2024 )
2025 .unwrap();
2026 let space_invisible = cx
2027 .text_system()
2028 .shape_line(
2029 "•".into(),
2030 invisible_symbol_font_size,
2031 &[TextRun {
2032 len: "•".len(),
2033 font: self.style.text.font(),
2034 color: cx.theme().colors().editor_invisible,
2035 background_color: None,
2036 underline: None,
2037 }],
2038 )
2039 .unwrap();
2040
2041 LayoutState {
2042 mode: snapshot.mode,
2043 position_map: Arc::new(PositionMap {
2044 size: bounds.size,
2045 scroll_position: point(
2046 scroll_position.x * em_width,
2047 scroll_position.y * line_height,
2048 ),
2049 scroll_max,
2050 line_layouts,
2051 line_height,
2052 em_width,
2053 em_advance,
2054 snapshot,
2055 }),
2056 visible_anchor_range: start_anchor..end_anchor,
2057 visible_display_row_range: start_row..end_row,
2058 wrap_guides,
2059 gutter_size,
2060 gutter_padding,
2061 text_size,
2062 scrollbar_row_range,
2063 show_scrollbars,
2064 is_singleton,
2065 max_row,
2066 gutter_margin,
2067 active_rows,
2068 highlighted_rows,
2069 highlighted_ranges,
2070 line_numbers,
2071 display_hunks,
2072 blocks,
2073 selections,
2074 context_menu,
2075 code_actions_indicator,
2076 fold_indicators,
2077 tab_invisible,
2078 space_invisible,
2079 hover_popovers: hover,
2080 }
2081 })
2082 }
2083
2084 #[allow(clippy::too_many_arguments)]
2085 fn layout_blocks(
2086 &self,
2087 rows: Range<u32>,
2088 snapshot: &EditorSnapshot,
2089 editor_width: Pixels,
2090 scroll_width: Pixels,
2091 gutter_padding: Pixels,
2092 gutter_width: Pixels,
2093 em_width: Pixels,
2094 text_x: Pixels,
2095 line_height: Pixels,
2096 style: &EditorStyle,
2097 line_layouts: &[LineWithInvisibles],
2098 editor: &mut Editor,
2099 cx: &mut ViewContext<Editor>,
2100 ) -> (Pixels, Vec<BlockLayout>) {
2101 let mut block_id = 0;
2102 let scroll_x = snapshot.scroll_anchor.offset.x;
2103 let (fixed_blocks, non_fixed_blocks) = snapshot
2104 .blocks_in_range(rows.clone())
2105 .partition::<Vec<_>, _>(|(_, block)| match block {
2106 TransformBlock::ExcerptHeader { .. } => false,
2107 TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
2108 });
2109
2110 let mut render_block = |block: &TransformBlock,
2111 available_space: Size<AvailableSpace>,
2112 block_id: usize,
2113 editor: &mut Editor,
2114 cx: &mut ViewContext<Editor>| {
2115 let mut element = match block {
2116 TransformBlock::Custom(block) => {
2117 let align_to = block
2118 .position()
2119 .to_point(&snapshot.buffer_snapshot)
2120 .to_display_point(snapshot);
2121 let anchor_x = text_x
2122 + if rows.contains(&align_to.row()) {
2123 line_layouts[(align_to.row() - rows.start) as usize]
2124 .line
2125 .x_for_index(align_to.column() as usize)
2126 } else {
2127 layout_line(align_to.row(), snapshot, style, cx)
2128 .unwrap()
2129 .x_for_index(align_to.column() as usize)
2130 };
2131
2132 block.render(&mut BlockContext {
2133 view_context: cx,
2134 anchor_x,
2135 gutter_padding,
2136 line_height,
2137 gutter_width,
2138 em_width,
2139 block_id,
2140 editor_style: &self.style,
2141 })
2142 }
2143
2144 TransformBlock::ExcerptHeader {
2145 buffer,
2146 range,
2147 starts_new_buffer,
2148 ..
2149 } => {
2150 let include_root = editor
2151 .project
2152 .as_ref()
2153 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
2154 .unwrap_or_default();
2155 let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
2156 let jump_path = ProjectPath {
2157 worktree_id: file.worktree_id(cx),
2158 path: file.path.clone(),
2159 };
2160 let jump_anchor = range
2161 .primary
2162 .as_ref()
2163 .map_or(range.context.start, |primary| primary.start);
2164 let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
2165
2166 IconButton::new(block_id, ui::Icon::ArrowUpRight)
2167 .on_click(cx.listener_for(&self.editor, move |editor, e, cx| {
2168 editor.jump(jump_path.clone(), jump_position, jump_anchor, cx);
2169 }))
2170 .tooltip(|cx| Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx))
2171 });
2172
2173 let element = if *starts_new_buffer {
2174 let path = buffer.resolve_file_path(cx, include_root);
2175 let mut filename = None;
2176 let mut parent_path = None;
2177 // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
2178 if let Some(path) = path {
2179 filename = path.file_name().map(|f| f.to_string_lossy().to_string());
2180 parent_path = path
2181 .parent()
2182 .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
2183 }
2184
2185 h_stack()
2186 .id("path header block")
2187 .size_full()
2188 .bg(gpui::red())
2189 .child(
2190 filename
2191 .map(SharedString::from)
2192 .unwrap_or_else(|| "untitled".into()),
2193 )
2194 .children(parent_path)
2195 .children(jump_icon) // .p_x(gutter_padding)
2196 } else {
2197 let text_style = style.text.clone();
2198 h_stack()
2199 .id("collapsed context")
2200 .size_full()
2201 .bg(gpui::red())
2202 .child("⋯")
2203 .children(jump_icon) // .p_x(gutter_padding)
2204 };
2205 element.into_any()
2206 }
2207 };
2208
2209 let size = element.measure(available_space, cx);
2210 (element, size)
2211 };
2212
2213 let mut fixed_block_max_width = Pixels::ZERO;
2214 let mut blocks = Vec::new();
2215 for (row, block) in fixed_blocks {
2216 let available_space = size(
2217 AvailableSpace::MinContent,
2218 AvailableSpace::Definite(block.height() as f32 * line_height),
2219 );
2220 let (element, element_size) =
2221 render_block(block, available_space, block_id, editor, cx);
2222 block_id += 1;
2223 fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
2224 blocks.push(BlockLayout {
2225 row,
2226 element,
2227 available_space,
2228 style: BlockStyle::Fixed,
2229 });
2230 }
2231 for (row, block) in non_fixed_blocks {
2232 let style = match block {
2233 TransformBlock::Custom(block) => block.style(),
2234 TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
2235 };
2236 let width = match style {
2237 BlockStyle::Sticky => editor_width,
2238 BlockStyle::Flex => editor_width
2239 .max(fixed_block_max_width)
2240 .max(gutter_width + scroll_width),
2241 BlockStyle::Fixed => unreachable!(),
2242 };
2243 let available_space = size(
2244 AvailableSpace::Definite(width),
2245 AvailableSpace::Definite(block.height() as f32 * line_height),
2246 );
2247 let (element, _) = render_block(block, available_space, block_id, editor, cx);
2248 block_id += 1;
2249 blocks.push(BlockLayout {
2250 row,
2251 element,
2252 available_space,
2253 style,
2254 });
2255 }
2256 (
2257 scroll_width.max(fixed_block_max_width - gutter_width),
2258 blocks,
2259 )
2260 }
2261
2262 fn paint_mouse_listeners(
2263 &mut self,
2264 bounds: Bounds<Pixels>,
2265 gutter_bounds: Bounds<Pixels>,
2266 text_bounds: Bounds<Pixels>,
2267 layout: &LayoutState,
2268 cx: &mut WindowContext,
2269 ) {
2270 let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
2271 let interactive_bounds = InteractiveBounds {
2272 bounds: bounds.intersect(&cx.content_mask().bounds),
2273 stacking_order: cx.stacking_order().clone(),
2274 };
2275
2276 cx.on_mouse_event({
2277 let position_map = layout.position_map.clone();
2278 let editor = self.editor.clone();
2279 let interactive_bounds = interactive_bounds.clone();
2280
2281 move |event: &ScrollWheelEvent, phase, cx| {
2282 if phase != DispatchPhase::Bubble {
2283 return;
2284 }
2285
2286 let should_cancel = editor.update(cx, |editor, cx| {
2287 Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
2288 });
2289 if should_cancel {
2290 cx.stop_propagation();
2291 }
2292 }
2293 });
2294
2295 cx.on_mouse_event({
2296 let position_map = layout.position_map.clone();
2297 let editor = self.editor.clone();
2298 let stacking_order = cx.stacking_order().clone();
2299
2300 move |event: &MouseDownEvent, phase, cx| {
2301 if phase != DispatchPhase::Bubble {
2302 return;
2303 }
2304
2305 let should_cancel = editor.update(cx, |editor, cx| {
2306 Self::mouse_down(
2307 editor,
2308 event,
2309 &position_map,
2310 text_bounds,
2311 gutter_bounds,
2312 &stacking_order,
2313 cx,
2314 )
2315 });
2316
2317 if should_cancel {
2318 cx.stop_propagation()
2319 }
2320 }
2321 });
2322
2323 cx.on_mouse_event({
2324 let position_map = layout.position_map.clone();
2325 let editor = self.editor.clone();
2326 let stacking_order = cx.stacking_order().clone();
2327
2328 move |event: &MouseUpEvent, phase, cx| {
2329 let should_cancel = editor.update(cx, |editor, cx| {
2330 Self::mouse_up(
2331 editor,
2332 event,
2333 &position_map,
2334 text_bounds,
2335 &stacking_order,
2336 cx,
2337 )
2338 });
2339
2340 if should_cancel {
2341 cx.stop_propagation()
2342 }
2343 }
2344 });
2345 //todo!()
2346 // on_down(MouseButton::Right, {
2347 // let position_map = layout.position_map.clone();
2348 // move |event, editor, cx| {
2349 // if !Self::mouse_right_down(
2350 // editor,
2351 // event.position,
2352 // position_map.as_ref(),
2353 // text_bounds,
2354 // cx,
2355 // ) {
2356 // cx.propagate_event();
2357 // }
2358 // }
2359 // });
2360 cx.on_mouse_event({
2361 let position_map = layout.position_map.clone();
2362 let editor = self.editor.clone();
2363 let stacking_order = cx.stacking_order().clone();
2364
2365 move |event: &MouseMoveEvent, phase, cx| {
2366 if phase != DispatchPhase::Bubble {
2367 return;
2368 }
2369
2370 let stop_propogating = editor.update(cx, |editor, cx| {
2371 Self::mouse_moved(
2372 editor,
2373 event,
2374 &position_map,
2375 text_bounds,
2376 gutter_bounds,
2377 &stacking_order,
2378 cx,
2379 )
2380 });
2381
2382 if stop_propogating {
2383 cx.stop_propagation()
2384 }
2385 }
2386 });
2387 }
2388}
2389
2390#[derive(Debug)]
2391pub struct LineWithInvisibles {
2392 pub line: ShapedLine,
2393 invisibles: Vec<Invisible>,
2394}
2395
2396impl LineWithInvisibles {
2397 fn from_chunks<'a>(
2398 chunks: impl Iterator<Item = HighlightedChunk<'a>>,
2399 text_style: &TextStyle,
2400 max_line_len: usize,
2401 max_line_count: usize,
2402 line_number_layouts: &[Option<ShapedLine>],
2403 editor_mode: EditorMode,
2404 cx: &WindowContext,
2405 ) -> Vec<Self> {
2406 let mut layouts = Vec::with_capacity(max_line_count);
2407 let mut line = String::new();
2408 let mut invisibles = Vec::new();
2409 let mut styles = Vec::new();
2410 let mut non_whitespace_added = false;
2411 let mut row = 0;
2412 let mut line_exceeded_max_len = false;
2413 let font_size = text_style.font_size.to_pixels(cx.rem_size());
2414
2415 for highlighted_chunk in chunks.chain([HighlightedChunk {
2416 chunk: "\n",
2417 style: None,
2418 is_tab: false,
2419 }]) {
2420 for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() {
2421 if ix > 0 {
2422 let shaped_line = cx
2423 .text_system()
2424 .shape_line(line.clone().into(), font_size, &styles)
2425 .unwrap();
2426 layouts.push(Self {
2427 line: shaped_line,
2428 invisibles: invisibles.drain(..).collect(),
2429 });
2430
2431 line.clear();
2432 styles.clear();
2433 row += 1;
2434 line_exceeded_max_len = false;
2435 non_whitespace_added = false;
2436 if row == max_line_count {
2437 return layouts;
2438 }
2439 }
2440
2441 if !line_chunk.is_empty() && !line_exceeded_max_len {
2442 let text_style = if let Some(style) = highlighted_chunk.style {
2443 Cow::Owned(text_style.clone().highlight(style))
2444 } else {
2445 Cow::Borrowed(text_style)
2446 };
2447
2448 if line.len() + line_chunk.len() > max_line_len {
2449 let mut chunk_len = max_line_len - line.len();
2450 while !line_chunk.is_char_boundary(chunk_len) {
2451 chunk_len -= 1;
2452 }
2453 line_chunk = &line_chunk[..chunk_len];
2454 line_exceeded_max_len = true;
2455 }
2456
2457 styles.push(TextRun {
2458 len: line_chunk.len(),
2459 font: text_style.font(),
2460 color: text_style.color,
2461 background_color: text_style.background_color,
2462 underline: text_style.underline,
2463 });
2464
2465 if editor_mode == EditorMode::Full {
2466 // Line wrap pads its contents with fake whitespaces,
2467 // avoid printing them
2468 let inside_wrapped_string = line_number_layouts
2469 .get(row)
2470 .and_then(|layout| layout.as_ref())
2471 .is_none();
2472 if highlighted_chunk.is_tab {
2473 if non_whitespace_added || !inside_wrapped_string {
2474 invisibles.push(Invisible::Tab {
2475 line_start_offset: line.len(),
2476 });
2477 }
2478 } else {
2479 invisibles.extend(
2480 line_chunk
2481 .chars()
2482 .enumerate()
2483 .filter(|(_, line_char)| {
2484 let is_whitespace = line_char.is_whitespace();
2485 non_whitespace_added |= !is_whitespace;
2486 is_whitespace
2487 && (non_whitespace_added || !inside_wrapped_string)
2488 })
2489 .map(|(whitespace_index, _)| Invisible::Whitespace {
2490 line_offset: line.len() + whitespace_index,
2491 }),
2492 )
2493 }
2494 }
2495
2496 line.push_str(line_chunk);
2497 }
2498 }
2499 }
2500
2501 layouts
2502 }
2503
2504 fn draw(
2505 &self,
2506 layout: &LayoutState,
2507 row: u32,
2508 content_origin: gpui::Point<Pixels>,
2509 whitespace_setting: ShowWhitespaceSetting,
2510 selection_ranges: &[Range<DisplayPoint>],
2511 cx: &mut WindowContext,
2512 ) {
2513 let line_height = layout.position_map.line_height;
2514 let line_y = line_height * row as f32 - layout.position_map.scroll_position.y;
2515
2516 self.line.paint(
2517 content_origin + gpui::point(-layout.position_map.scroll_position.x, line_y),
2518 line_height,
2519 cx,
2520 );
2521
2522 self.draw_invisibles(
2523 &selection_ranges,
2524 layout,
2525 content_origin,
2526 line_y,
2527 row,
2528 line_height,
2529 whitespace_setting,
2530 cx,
2531 );
2532 }
2533
2534 fn draw_invisibles(
2535 &self,
2536 selection_ranges: &[Range<DisplayPoint>],
2537 layout: &LayoutState,
2538 content_origin: gpui::Point<Pixels>,
2539 line_y: Pixels,
2540 row: u32,
2541 line_height: Pixels,
2542 whitespace_setting: ShowWhitespaceSetting,
2543 cx: &mut WindowContext,
2544 ) {
2545 let allowed_invisibles_regions = match whitespace_setting {
2546 ShowWhitespaceSetting::None => return,
2547 ShowWhitespaceSetting::Selection => Some(selection_ranges),
2548 ShowWhitespaceSetting::All => None,
2549 };
2550
2551 for invisible in &self.invisibles {
2552 let (&token_offset, invisible_symbol) = match invisible {
2553 Invisible::Tab { line_start_offset } => (line_start_offset, &layout.tab_invisible),
2554 Invisible::Whitespace { line_offset } => (line_offset, &layout.space_invisible),
2555 };
2556
2557 let x_offset = self.line.x_for_index(token_offset);
2558 let invisible_offset =
2559 (layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
2560 let origin = content_origin
2561 + gpui::point(
2562 x_offset + invisible_offset - layout.position_map.scroll_position.x,
2563 line_y,
2564 );
2565
2566 if let Some(allowed_regions) = allowed_invisibles_regions {
2567 let invisible_point = DisplayPoint::new(row, token_offset as u32);
2568 if !allowed_regions
2569 .iter()
2570 .any(|region| region.start <= invisible_point && invisible_point < region.end)
2571 {
2572 continue;
2573 }
2574 }
2575 invisible_symbol.paint(origin, line_height, cx);
2576 }
2577 }
2578}
2579
2580#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2581enum Invisible {
2582 Tab { line_start_offset: usize },
2583 Whitespace { line_offset: usize },
2584}
2585
2586impl Element for EditorElement {
2587 type State = ();
2588
2589 fn layout(
2590 &mut self,
2591 element_state: Option<Self::State>,
2592 cx: &mut gpui::WindowContext,
2593 ) -> (gpui::LayoutId, Self::State) {
2594 self.editor.update(cx, |editor, cx| {
2595 editor.set_style(self.style.clone(), cx);
2596
2597 let layout_id = match editor.mode {
2598 EditorMode::SingleLine => {
2599 let rem_size = cx.rem_size();
2600 let mut style = Style::default();
2601 style.size.width = relative(1.).into();
2602 style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
2603 cx.request_layout(&style, None)
2604 }
2605 EditorMode::AutoHeight { max_lines } => {
2606 let editor_handle = cx.view().clone();
2607 let max_line_number_width =
2608 self.max_line_number_width(&editor.snapshot(cx), cx);
2609 cx.request_measured_layout(
2610 Style::default(),
2611 move |known_dimensions, available_space, cx| {
2612 editor_handle
2613 .update(cx, |editor, cx| {
2614 compute_auto_height_layout(
2615 editor,
2616 max_lines,
2617 max_line_number_width,
2618 known_dimensions,
2619 cx,
2620 )
2621 })
2622 .unwrap_or_default()
2623 },
2624 )
2625 }
2626 EditorMode::Full => {
2627 let mut style = Style::default();
2628 style.size.width = relative(1.).into();
2629 style.size.height = relative(1.).into();
2630 cx.request_layout(&style, None)
2631 }
2632 };
2633
2634 (layout_id, ())
2635 })
2636 }
2637
2638 fn paint(
2639 mut self,
2640 bounds: Bounds<gpui::Pixels>,
2641 element_state: &mut Self::State,
2642 cx: &mut gpui::WindowContext,
2643 ) {
2644 let editor = self.editor.clone();
2645
2646 let mut layout = self.compute_layout(bounds, cx);
2647 let gutter_bounds = Bounds {
2648 origin: bounds.origin,
2649 size: layout.gutter_size,
2650 };
2651 let text_bounds = Bounds {
2652 origin: gutter_bounds.upper_right(),
2653 size: layout.text_size,
2654 };
2655
2656 let focus_handle = editor.focus_handle(cx);
2657 let dispatch_context = self.editor.read(cx).dispatch_context(cx);
2658 cx.with_key_dispatch(dispatch_context, Some(focus_handle.clone()), |_, cx| {
2659 self.register_actions(cx);
2660
2661 // We call with_z_index to establish a new stacking context.
2662 cx.with_z_index(0, |cx| {
2663 cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
2664 // Paint mouse listeners at z-index 0 so any elements we paint on top of the editor
2665 // take precedence.
2666 cx.with_z_index(0, |cx| {
2667 self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);
2668 });
2669 let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx);
2670 cx.handle_input(&focus_handle, input_handler);
2671
2672 self.paint_background(gutter_bounds, text_bounds, &layout, cx);
2673 if layout.gutter_size.width > Pixels::ZERO {
2674 self.paint_gutter(gutter_bounds, &mut layout, cx);
2675 }
2676 self.paint_text(text_bounds, &mut layout, cx);
2677
2678 if !layout.blocks.is_empty() {
2679 cx.with_element_id(Some("editor_blocks"), |cx| {
2680 self.paint_blocks(bounds, &mut layout, cx);
2681 })
2682 }
2683 });
2684 });
2685 })
2686 }
2687}
2688
2689impl IntoElement for EditorElement {
2690 type Element = Self;
2691
2692 fn element_id(&self) -> Option<gpui::ElementId> {
2693 self.editor.element_id()
2694 }
2695
2696 fn into_element(self) -> Self::Element {
2697 self
2698 }
2699}
2700
2701type BufferRow = u32;
2702
2703pub struct LayoutState {
2704 position_map: Arc<PositionMap>,
2705 gutter_size: Size<Pixels>,
2706 gutter_padding: Pixels,
2707 gutter_margin: Pixels,
2708 text_size: gpui::Size<Pixels>,
2709 mode: EditorMode,
2710 wrap_guides: SmallVec<[(Pixels, bool); 2]>,
2711 visible_anchor_range: Range<Anchor>,
2712 visible_display_row_range: Range<u32>,
2713 active_rows: BTreeMap<u32, bool>,
2714 highlighted_rows: Option<Range<u32>>,
2715 line_numbers: Vec<Option<ShapedLine>>,
2716 display_hunks: Vec<DisplayDiffHunk>,
2717 blocks: Vec<BlockLayout>,
2718 highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
2719 selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
2720 scrollbar_row_range: Range<f32>,
2721 show_scrollbars: bool,
2722 is_singleton: bool,
2723 max_row: u32,
2724 context_menu: Option<(DisplayPoint, AnyElement)>,
2725 code_actions_indicator: Option<CodeActionsIndicator>,
2726 hover_popovers: Option<(DisplayPoint, Vec<AnyElement>)>,
2727 fold_indicators: Vec<Option<IconButton>>,
2728 tab_invisible: ShapedLine,
2729 space_invisible: ShapedLine,
2730}
2731
2732struct CodeActionsIndicator {
2733 row: u32,
2734 button: IconButton,
2735}
2736
2737struct PositionMap {
2738 size: Size<Pixels>,
2739 line_height: Pixels,
2740 scroll_position: gpui::Point<Pixels>,
2741 scroll_max: gpui::Point<f32>,
2742 em_width: Pixels,
2743 em_advance: Pixels,
2744 line_layouts: Vec<LineWithInvisibles>,
2745 snapshot: EditorSnapshot,
2746}
2747
2748#[derive(Debug, Copy, Clone)]
2749pub struct PointForPosition {
2750 pub previous_valid: DisplayPoint,
2751 pub next_valid: DisplayPoint,
2752 pub exact_unclipped: DisplayPoint,
2753 pub column_overshoot_after_line_end: u32,
2754}
2755
2756impl PointForPosition {
2757 #[cfg(test)]
2758 pub fn valid(valid: DisplayPoint) -> Self {
2759 Self {
2760 previous_valid: valid,
2761 next_valid: valid,
2762 exact_unclipped: valid,
2763 column_overshoot_after_line_end: 0,
2764 }
2765 }
2766
2767 pub fn as_valid(&self) -> Option<DisplayPoint> {
2768 if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped {
2769 Some(self.previous_valid)
2770 } else {
2771 None
2772 }
2773 }
2774}
2775
2776impl PositionMap {
2777 fn point_for_position(
2778 &self,
2779 text_bounds: Bounds<Pixels>,
2780 position: gpui::Point<Pixels>,
2781 ) -> PointForPosition {
2782 let scroll_position = self.snapshot.scroll_position();
2783 let position = position - text_bounds.origin;
2784 let y = position.y.max(px(0.)).min(self.size.width);
2785 let x = position.x + (scroll_position.x * self.em_width);
2786 let row = (f32::from(y / self.line_height) + scroll_position.y) as u32;
2787
2788 let (column, x_overshoot_after_line_end) = if let Some(line) = self
2789 .line_layouts
2790 .get(row as usize - scroll_position.y as usize)
2791 .map(|&LineWithInvisibles { ref line, .. }| line)
2792 {
2793 if let Some(ix) = line.index_for_x(x) {
2794 (ix as u32, px(0.))
2795 } else {
2796 (line.len as u32, px(0.).max(x - line.width))
2797 }
2798 } else {
2799 (0, x)
2800 };
2801
2802 let mut exact_unclipped = DisplayPoint::new(row, column);
2803 let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
2804 let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
2805
2806 let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32;
2807 *exact_unclipped.column_mut() += column_overshoot_after_line_end;
2808 PointForPosition {
2809 previous_valid,
2810 next_valid,
2811 exact_unclipped,
2812 column_overshoot_after_line_end,
2813 }
2814 }
2815}
2816
2817struct BlockLayout {
2818 row: u32,
2819 element: AnyElement,
2820 available_space: Size<AvailableSpace>,
2821 style: BlockStyle,
2822}
2823
2824fn layout_line(
2825 row: u32,
2826 snapshot: &EditorSnapshot,
2827 style: &EditorStyle,
2828 cx: &WindowContext,
2829) -> Result<ShapedLine> {
2830 let mut line = snapshot.line(row);
2831
2832 if line.len() > MAX_LINE_LEN {
2833 let mut len = MAX_LINE_LEN;
2834 while !line.is_char_boundary(len) {
2835 len -= 1;
2836 }
2837
2838 line.truncate(len);
2839 }
2840
2841 cx.text_system().shape_line(
2842 line.into(),
2843 style.text.font_size.to_pixels(cx.rem_size()),
2844 &[TextRun {
2845 len: snapshot.line_len(row) as usize,
2846 font: style.text.font(),
2847 color: Hsla::default(),
2848 background_color: None,
2849 underline: None,
2850 }],
2851 )
2852}
2853
2854#[derive(Debug)]
2855pub struct Cursor {
2856 origin: gpui::Point<Pixels>,
2857 block_width: Pixels,
2858 line_height: Pixels,
2859 color: Hsla,
2860 shape: CursorShape,
2861 block_text: Option<ShapedLine>,
2862}
2863
2864impl Cursor {
2865 pub fn new(
2866 origin: gpui::Point<Pixels>,
2867 block_width: Pixels,
2868 line_height: Pixels,
2869 color: Hsla,
2870 shape: CursorShape,
2871 block_text: Option<ShapedLine>,
2872 ) -> Cursor {
2873 Cursor {
2874 origin,
2875 block_width,
2876 line_height,
2877 color,
2878 shape,
2879 block_text,
2880 }
2881 }
2882
2883 pub fn bounding_rect(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
2884 Bounds {
2885 origin: self.origin + origin,
2886 size: size(self.block_width, self.line_height),
2887 }
2888 }
2889
2890 pub fn paint(&self, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
2891 let bounds = match self.shape {
2892 CursorShape::Bar => Bounds {
2893 origin: self.origin + origin,
2894 size: size(px(2.0), self.line_height),
2895 },
2896 CursorShape::Block | CursorShape::Hollow => Bounds {
2897 origin: self.origin + origin,
2898 size: size(self.block_width, self.line_height),
2899 },
2900 CursorShape::Underscore => Bounds {
2901 origin: self.origin
2902 + origin
2903 + gpui::Point::new(Pixels::ZERO, self.line_height - px(2.0)),
2904 size: size(self.block_width, px(2.0)),
2905 },
2906 };
2907
2908 //Draw background or border quad
2909 if matches!(self.shape, CursorShape::Hollow) {
2910 cx.paint_quad(
2911 bounds,
2912 Corners::default(),
2913 transparent_black(),
2914 Edges::all(px(1.)),
2915 self.color,
2916 );
2917 } else {
2918 cx.paint_quad(
2919 bounds,
2920 Corners::default(),
2921 self.color,
2922 Edges::default(),
2923 transparent_black(),
2924 );
2925 }
2926
2927 if let Some(block_text) = &self.block_text {
2928 block_text.paint(self.origin + origin, self.line_height, cx);
2929 }
2930 }
2931
2932 pub fn shape(&self) -> CursorShape {
2933 self.shape
2934 }
2935}
2936
2937#[derive(Debug)]
2938pub struct HighlightedRange {
2939 pub start_y: Pixels,
2940 pub line_height: Pixels,
2941 pub lines: Vec<HighlightedRangeLine>,
2942 pub color: Hsla,
2943 pub corner_radius: Pixels,
2944}
2945
2946#[derive(Debug)]
2947pub struct HighlightedRangeLine {
2948 pub start_x: Pixels,
2949 pub end_x: Pixels,
2950}
2951
2952impl HighlightedRange {
2953 pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
2954 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
2955 self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
2956 self.paint_lines(
2957 self.start_y + self.line_height,
2958 &self.lines[1..],
2959 bounds,
2960 cx,
2961 );
2962 } else {
2963 self.paint_lines(self.start_y, &self.lines, bounds, cx);
2964 }
2965 }
2966
2967 fn paint_lines(
2968 &self,
2969 start_y: Pixels,
2970 lines: &[HighlightedRangeLine],
2971 bounds: Bounds<Pixels>,
2972 cx: &mut WindowContext,
2973 ) {
2974 if lines.is_empty() {
2975 return;
2976 }
2977
2978 let first_line = lines.first().unwrap();
2979 let last_line = lines.last().unwrap();
2980
2981 let first_top_left = point(first_line.start_x, start_y);
2982 let first_top_right = point(first_line.end_x, start_y);
2983
2984 let curve_height = point(Pixels::ZERO, self.corner_radius);
2985 let curve_width = |start_x: Pixels, end_x: Pixels| {
2986 let max = (end_x - start_x) / 2.;
2987 let width = if max < self.corner_radius {
2988 max
2989 } else {
2990 self.corner_radius
2991 };
2992
2993 point(width, Pixels::ZERO)
2994 };
2995
2996 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
2997 let mut path = gpui::Path::new(first_top_right - top_curve_width);
2998 path.curve_to(first_top_right + curve_height, first_top_right);
2999
3000 let mut iter = lines.iter().enumerate().peekable();
3001 while let Some((ix, line)) = iter.next() {
3002 let bottom_right = point(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
3003
3004 if let Some((_, next_line)) = iter.peek() {
3005 let next_top_right = point(next_line.end_x, bottom_right.y);
3006
3007 match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
3008 Ordering::Equal => {
3009 path.line_to(bottom_right);
3010 }
3011 Ordering::Less => {
3012 let curve_width = curve_width(next_top_right.x, bottom_right.x);
3013 path.line_to(bottom_right - curve_height);
3014 if self.corner_radius > Pixels::ZERO {
3015 path.curve_to(bottom_right - curve_width, bottom_right);
3016 }
3017 path.line_to(next_top_right + curve_width);
3018 if self.corner_radius > Pixels::ZERO {
3019 path.curve_to(next_top_right + curve_height, next_top_right);
3020 }
3021 }
3022 Ordering::Greater => {
3023 let curve_width = curve_width(bottom_right.x, next_top_right.x);
3024 path.line_to(bottom_right - curve_height);
3025 if self.corner_radius > Pixels::ZERO {
3026 path.curve_to(bottom_right + curve_width, bottom_right);
3027 }
3028 path.line_to(next_top_right - curve_width);
3029 if self.corner_radius > Pixels::ZERO {
3030 path.curve_to(next_top_right + curve_height, next_top_right);
3031 }
3032 }
3033 }
3034 } else {
3035 let curve_width = curve_width(line.start_x, line.end_x);
3036 path.line_to(bottom_right - curve_height);
3037 if self.corner_radius > Pixels::ZERO {
3038 path.curve_to(bottom_right - curve_width, bottom_right);
3039 }
3040
3041 let bottom_left = point(line.start_x, bottom_right.y);
3042 path.line_to(bottom_left + curve_width);
3043 if self.corner_radius > Pixels::ZERO {
3044 path.curve_to(bottom_left - curve_height, bottom_left);
3045 }
3046 }
3047 }
3048
3049 if first_line.start_x > last_line.start_x {
3050 let curve_width = curve_width(last_line.start_x, first_line.start_x);
3051 let second_top_left = point(last_line.start_x, start_y + self.line_height);
3052 path.line_to(second_top_left + curve_height);
3053 if self.corner_radius > Pixels::ZERO {
3054 path.curve_to(second_top_left + curve_width, second_top_left);
3055 }
3056 let first_bottom_left = point(first_line.start_x, second_top_left.y);
3057 path.line_to(first_bottom_left - curve_width);
3058 if self.corner_radius > Pixels::ZERO {
3059 path.curve_to(first_bottom_left - curve_height, first_bottom_left);
3060 }
3061 }
3062
3063 path.line_to(first_top_left + curve_height);
3064 if self.corner_radius > Pixels::ZERO {
3065 path.curve_to(first_top_left + top_curve_width, first_top_left);
3066 }
3067 path.line_to(first_top_right - top_curve_width);
3068
3069 cx.paint_path(path, self.color);
3070 }
3071}
3072
3073pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
3074 (delta.pow(1.5) / 100.0).into()
3075}
3076
3077fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
3078 (delta.pow(1.2) / 300.0).into()
3079}
3080
3081// #[cfg(test)]
3082// mod tests {
3083// use super::*;
3084// use crate::{
3085// display_map::{BlockDisposition, BlockProperties},
3086// editor_tests::{init_test, update_test_language_settings},
3087// Editor, MultiBuffer,
3088// };
3089// use gpui::TestAppContext;
3090// use language::language_settings;
3091// use log::info;
3092// use std::{num::NonZeroU32, sync::Arc};
3093// use util::test::sample_text;
3094
3095// #[gpui::test]
3096// fn test_layout_line_numbers(cx: &mut TestAppContext) {
3097// init_test(cx, |_| {});
3098// let editor = cx
3099// .add_window(|cx| {
3100// let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
3101// Editor::new(EditorMode::Full, buffer, None, None, cx)
3102// })
3103// .root(cx);
3104// let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
3105
3106// let layouts = editor.update(cx, |editor, cx| {
3107// let snapshot = editor.snapshot(cx);
3108// element
3109// .layout_line_numbers(
3110// 0..6,
3111// &Default::default(),
3112// DisplayPoint::new(0, 0),
3113// false,
3114// &snapshot,
3115// cx,
3116// )
3117// .0
3118// });
3119// assert_eq!(layouts.len(), 6);
3120
3121// let relative_rows = editor.update(cx, |editor, cx| {
3122// let snapshot = editor.snapshot(cx);
3123// element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3))
3124// });
3125// assert_eq!(relative_rows[&0], 3);
3126// assert_eq!(relative_rows[&1], 2);
3127// assert_eq!(relative_rows[&2], 1);
3128// // current line has no relative number
3129// assert_eq!(relative_rows[&4], 1);
3130// assert_eq!(relative_rows[&5], 2);
3131
3132// // works if cursor is before screen
3133// let relative_rows = editor.update(cx, |editor, cx| {
3134// let snapshot = editor.snapshot(cx);
3135
3136// element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1))
3137// });
3138// assert_eq!(relative_rows.len(), 3);
3139// assert_eq!(relative_rows[&3], 2);
3140// assert_eq!(relative_rows[&4], 3);
3141// assert_eq!(relative_rows[&5], 4);
3142
3143// // works if cursor is after screen
3144// let relative_rows = editor.update(cx, |editor, cx| {
3145// let snapshot = editor.snapshot(cx);
3146
3147// element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6))
3148// });
3149// assert_eq!(relative_rows.len(), 3);
3150// assert_eq!(relative_rows[&0], 5);
3151// assert_eq!(relative_rows[&1], 4);
3152// assert_eq!(relative_rows[&2], 3);
3153// }
3154
3155// #[gpui::test]
3156// async fn test_vim_visual_selections(cx: &mut TestAppContext) {
3157// init_test(cx, |_| {});
3158
3159// let editor = cx
3160// .add_window(|cx| {
3161// let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
3162// Editor::new(EditorMode::Full, buffer, None, None, cx)
3163// })
3164// .root(cx);
3165// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
3166// let (_, state) = editor.update(cx, |editor, cx| {
3167// editor.cursor_shape = CursorShape::Block;
3168// editor.change_selections(None, cx, |s| {
3169// s.select_ranges([
3170// Point::new(0, 0)..Point::new(1, 0),
3171// Point::new(3, 2)..Point::new(3, 3),
3172// Point::new(5, 6)..Point::new(6, 0),
3173// ]);
3174// });
3175// element.layout(
3176// SizeConstraint::new(point(500., 500.), point(500., 500.)),
3177// editor,
3178// cx,
3179// )
3180// });
3181// assert_eq!(state.selections.len(), 1);
3182// let local_selections = &state.selections[0].1;
3183// assert_eq!(local_selections.len(), 3);
3184// // moves cursor back one line
3185// assert_eq!(local_selections[0].head, DisplayPoint::new(0, 6));
3186// assert_eq!(
3187// local_selections[0].range,
3188// DisplayPoint::new(0, 0)..DisplayPoint::new(1, 0)
3189// );
3190
3191// // moves cursor back one column
3192// assert_eq!(
3193// local_selections[1].range,
3194// DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3)
3195// );
3196// assert_eq!(local_selections[1].head, DisplayPoint::new(3, 2));
3197
3198// // leaves cursor on the max point
3199// assert_eq!(
3200// local_selections[2].range,
3201// DisplayPoint::new(5, 6)..DisplayPoint::new(6, 0)
3202// );
3203// assert_eq!(local_selections[2].head, DisplayPoint::new(6, 0));
3204
3205// // active lines does not include 1 (even though the range of the selection does)
3206// assert_eq!(
3207// state.active_rows.keys().cloned().collect::<Vec<u32>>(),
3208// vec![0, 3, 5, 6]
3209// );
3210
3211// // multi-buffer support
3212// // in DisplayPoint co-ordinates, this is what we're dealing with:
3213// // 0: [[file
3214// // 1: header]]
3215// // 2: aaaaaa
3216// // 3: bbbbbb
3217// // 4: cccccc
3218// // 5:
3219// // 6: ...
3220// // 7: ffffff
3221// // 8: gggggg
3222// // 9: hhhhhh
3223// // 10:
3224// // 11: [[file
3225// // 12: header]]
3226// // 13: bbbbbb
3227// // 14: cccccc
3228// // 15: dddddd
3229// let editor = cx
3230// .add_window(|cx| {
3231// let buffer = MultiBuffer::build_multi(
3232// [
3233// (
3234// &(sample_text(8, 6, 'a') + "\n"),
3235// vec![
3236// Point::new(0, 0)..Point::new(3, 0),
3237// Point::new(4, 0)..Point::new(7, 0),
3238// ],
3239// ),
3240// (
3241// &(sample_text(8, 6, 'a') + "\n"),
3242// vec![Point::new(1, 0)..Point::new(3, 0)],
3243// ),
3244// ],
3245// cx,
3246// );
3247// Editor::new(EditorMode::Full, buffer, None, None, cx)
3248// })
3249// .root(cx);
3250// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
3251// let (_, state) = editor.update(cx, |editor, cx| {
3252// editor.cursor_shape = CursorShape::Block;
3253// editor.change_selections(None, cx, |s| {
3254// s.select_display_ranges([
3255// DisplayPoint::new(4, 0)..DisplayPoint::new(7, 0),
3256// DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
3257// ]);
3258// });
3259// element.layout(
3260// SizeConstraint::new(point(500., 500.), point(500., 500.)),
3261// editor,
3262// cx,
3263// )
3264// });
3265
3266// assert_eq!(state.selections.len(), 1);
3267// let local_selections = &state.selections[0].1;
3268// assert_eq!(local_selections.len(), 2);
3269
3270// // moves cursor on excerpt boundary back a line
3271// // and doesn't allow selection to bleed through
3272// assert_eq!(
3273// local_selections[0].range,
3274// DisplayPoint::new(4, 0)..DisplayPoint::new(6, 0)
3275// );
3276// assert_eq!(local_selections[0].head, DisplayPoint::new(5, 0));
3277
3278// // moves cursor on buffer boundary back two lines
3279// // and doesn't allow selection to bleed through
3280// assert_eq!(
3281// local_selections[1].range,
3282// DisplayPoint::new(10, 0)..DisplayPoint::new(11, 0)
3283// );
3284// assert_eq!(local_selections[1].head, DisplayPoint::new(10, 0));
3285// }
3286
3287// #[gpui::test]
3288// fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
3289// init_test(cx, |_| {});
3290
3291// let editor = cx
3292// .add_window(|cx| {
3293// let buffer = MultiBuffer::build_simple("", cx);
3294// Editor::new(EditorMode::Full, buffer, None, None, cx)
3295// })
3296// .root(cx);
3297
3298// editor.update(cx, |editor, cx| {
3299// editor.set_placeholder_text("hello", cx);
3300// editor.insert_blocks(
3301// [BlockProperties {
3302// style: BlockStyle::Fixed,
3303// disposition: BlockDisposition::Above,
3304// height: 3,
3305// position: Anchor::min(),
3306// render: Arc::new(|_| Empty::new().into_any),
3307// }],
3308// None,
3309// cx,
3310// );
3311
3312// // Blur the editor so that it displays placeholder text.
3313// cx.blur();
3314// });
3315
3316// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
3317// let (size, mut state) = editor.update(cx, |editor, cx| {
3318// element.layout(
3319// SizeConstraint::new(point(500., 500.), point(500., 500.)),
3320// editor,
3321// cx,
3322// )
3323// });
3324
3325// assert_eq!(state.position_map.line_layouts.len(), 4);
3326// assert_eq!(
3327// state
3328// .line_number_layouts
3329// .iter()
3330// .map(Option::is_some)
3331// .collect::<Vec<_>>(),
3332// &[false, false, false, true]
3333// );
3334
3335// // Don't panic.
3336// let bounds = Bounds::<Pixels>::new(Default::default(), size);
3337// editor.update(cx, |editor, cx| {
3338// element.paint(bounds, bounds, &mut state, editor, cx);
3339// });
3340// }
3341
3342// #[gpui::test]
3343// fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
3344// const TAB_SIZE: u32 = 4;
3345
3346// let input_text = "\t \t|\t| a b";
3347// let expected_invisibles = vec![
3348// Invisible::Tab {
3349// line_start_offset: 0,
3350// },
3351// Invisible::Whitespace {
3352// line_offset: TAB_SIZE as usize,
3353// },
3354// Invisible::Tab {
3355// line_start_offset: TAB_SIZE as usize + 1,
3356// },
3357// Invisible::Tab {
3358// line_start_offset: TAB_SIZE as usize * 2 + 1,
3359// },
3360// Invisible::Whitespace {
3361// line_offset: TAB_SIZE as usize * 3 + 1,
3362// },
3363// Invisible::Whitespace {
3364// line_offset: TAB_SIZE as usize * 3 + 3,
3365// },
3366// ];
3367// assert_eq!(
3368// expected_invisibles.len(),
3369// input_text
3370// .chars()
3371// .filter(|initial_char| initial_char.is_whitespace())
3372// .count(),
3373// "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
3374// );
3375
3376// init_test(cx, |s| {
3377// s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
3378// s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
3379// });
3380
3381// let actual_invisibles =
3382// collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, 500.0);
3383
3384// assert_eq!(expected_invisibles, actual_invisibles);
3385// }
3386
3387// #[gpui::test]
3388// fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
3389// init_test(cx, |s| {
3390// s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
3391// s.defaults.tab_size = NonZeroU32::new(4);
3392// });
3393
3394// for editor_mode_without_invisibles in [
3395// EditorMode::SingleLine,
3396// EditorMode::AutoHeight { max_lines: 100 },
3397// ] {
3398// let invisibles = collect_invisibles_from_new_editor(
3399// cx,
3400// editor_mode_without_invisibles,
3401// "\t\t\t| | a b",
3402// 500.0,
3403// );
3404// assert!(invisibles.is_empty,
3405// "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}");
3406// }
3407// }
3408
3409// #[gpui::test]
3410// fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
3411// let tab_size = 4;
3412// let input_text = "a\tbcd ".repeat(9);
3413// let repeated_invisibles = [
3414// Invisible::Tab {
3415// line_start_offset: 1,
3416// },
3417// Invisible::Whitespace {
3418// line_offset: tab_size as usize + 3,
3419// },
3420// Invisible::Whitespace {
3421// line_offset: tab_size as usize + 4,
3422// },
3423// Invisible::Whitespace {
3424// line_offset: tab_size as usize + 5,
3425// },
3426// ];
3427// let expected_invisibles = std::iter::once(repeated_invisibles)
3428// .cycle()
3429// .take(9)
3430// .flatten()
3431// .collect::<Vec<_>>();
3432// assert_eq!(
3433// expected_invisibles.len(),
3434// input_text
3435// .chars()
3436// .filter(|initial_char| initial_char.is_whitespace())
3437// .count(),
3438// "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
3439// );
3440// info!("Expected invisibles: {expected_invisibles:?}");
3441
3442// init_test(cx, |_| {});
3443
3444// // Put the same string with repeating whitespace pattern into editors of various size,
3445// // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
3446// let resize_step = 10.0;
3447// let mut editor_width = 200.0;
3448// while editor_width <= 1000.0 {
3449// update_test_language_settings(cx, |s| {
3450// s.defaults.tab_size = NonZeroU32::new(tab_size);
3451// s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
3452// s.defaults.preferred_line_length = Some(editor_width as u32);
3453// s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
3454// });
3455
3456// let actual_invisibles =
3457// collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, editor_width);
3458
3459// // Whatever the editor size is, ensure it has the same invisible kinds in the same order
3460// // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
3461// let mut i = 0;
3462// for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
3463// i = actual_index;
3464// match expected_invisibles.get(i) {
3465// Some(expected_invisible) => match (expected_invisible, actual_invisible) {
3466// (Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
3467// | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
3468// _ => {
3469// panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}")
3470// }
3471// },
3472// None => panic!("Unexpected extra invisible {actual_invisible:?} at index {i}"),
3473// }
3474// }
3475// let missing_expected_invisibles = &expected_invisibles[i + 1..];
3476// assert!(
3477// missing_expected_invisibles.is_empty,
3478// "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
3479// );
3480
3481// editor_width += resize_step;
3482// }
3483// }
3484
3485// fn collect_invisibles_from_new_editor(
3486// cx: &mut TestAppContext,
3487// editor_mode: EditorMode,
3488// input_text: &str,
3489// editor_width: f32,
3490// ) -> Vec<Invisible> {
3491// info!(
3492// "Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'"
3493// );
3494// let editor = cx
3495// .add_window(|cx| {
3496// let buffer = MultiBuffer::build_simple(&input_text, cx);
3497// Editor::new(editor_mode, buffer, None, None, cx)
3498// })
3499// .root(cx);
3500
3501// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
3502// let (_, layout_state) = editor.update(cx, |editor, cx| {
3503// editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
3504// editor.set_wrap_width(Some(editor_width), cx);
3505
3506// element.layout(
3507// SizeConstraint::new(point(editor_width, 500.), point(editor_width, 500.)),
3508// editor,
3509// cx,
3510// )
3511// });
3512
3513// layout_state
3514// .position_map
3515// .line_layouts
3516// .iter()
3517// .map(|line_with_invisibles| &line_with_invisibles.invisibles)
3518// .flatten()
3519// .cloned()
3520// .collect()
3521// }
3522// }
3523
3524pub fn register_action<T: Action>(
3525 view: &View<Editor>,
3526 cx: &mut WindowContext,
3527 listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
3528) {
3529 let view = view.clone();
3530 cx.on_action(TypeId::of::<T>(), move |action, phase, cx| {
3531 let action = action.downcast_ref().unwrap();
3532 if phase == DispatchPhase::Bubble {
3533 view.update(cx, |editor, cx| {
3534 listener(editor, action, cx);
3535 })
3536 }
3537 })
3538}
3539
3540fn compute_auto_height_layout(
3541 editor: &mut Editor,
3542 max_lines: usize,
3543 max_line_number_width: Pixels,
3544 known_dimensions: Size<Option<Pixels>>,
3545 cx: &mut ViewContext<Editor>,
3546) -> Option<Size<Pixels>> {
3547 let mut width = known_dimensions.width?;
3548 if let Some(height) = known_dimensions.height {
3549 return Some(size(width, height));
3550 }
3551
3552 let style = editor.style.as_ref().unwrap();
3553 let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
3554 let font_size = style.text.font_size.to_pixels(cx.rem_size());
3555 let line_height = style.text.line_height_in_pixels(cx.rem_size());
3556 let em_width = cx
3557 .text_system()
3558 .typographic_bounds(font_id, font_size, 'm')
3559 .unwrap()
3560 .size
3561 .width;
3562
3563 let mut snapshot = editor.snapshot(cx);
3564 let gutter_padding;
3565 let gutter_width;
3566 let gutter_margin;
3567 if snapshot.show_gutter {
3568 let descent = cx.text_system().descent(font_id, font_size).unwrap();
3569 let gutter_padding_factor = 3.5;
3570 gutter_padding = (em_width * gutter_padding_factor).round();
3571 gutter_width = max_line_number_width + gutter_padding * 2.0;
3572 gutter_margin = -descent;
3573 } else {
3574 gutter_padding = Pixels::ZERO;
3575 gutter_width = Pixels::ZERO;
3576 gutter_margin = Pixels::ZERO;
3577 };
3578
3579 editor.gutter_width = gutter_width;
3580 let text_width = width - gutter_width;
3581 let overscroll = size(em_width, px(0.));
3582
3583 let editor_width = text_width - gutter_margin - overscroll.width - em_width;
3584 if editor.set_wrap_width(Some(editor_width), cx) {
3585 snapshot = editor.snapshot(cx);
3586 }
3587
3588 let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
3589 let height = scroll_height
3590 .max(line_height)
3591 .min(line_height * max_lines as f32);
3592
3593 Some(size(width, height))
3594}