1use collections::{hash_map, HashMap, HashSet};
2use git::diff::DiffHunkStatus;
3use gpui::{
4 AppContext, ClickEvent, CursorStyle, FocusableView, Hsla, Model, MouseButton, Task, View,
5};
6use language::{Buffer, BufferId, Point};
7use multi_buffer::{
8 Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow,
9 MultiBufferSnapshot, ToPoint,
10};
11use std::{ops::Range, sync::Arc};
12use text::OffsetRangeExt;
13use ui::{
14 prelude::*, ActiveTheme, IconButtonShape, InteractiveElement, IntoElement, KeyBinding,
15 ParentElement, Styled, TintColor, Tooltip, ViewContext, VisualContext,
16};
17use util::RangeExt;
18use workspace::Item;
19
20use crate::{
21 editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, is_hunk_selected,
22 ApplyAllDiffHunks, ApplySelectedDiffHunks, BlockPlacement, BlockProperties, BlockStyle,
23 CustomBlockId, DiffRowHighlight, DisplayRow, DisplaySnapshot, Editor, EditorElement,
24 ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk, RevertSelectedHunks, ToDisplayPoint,
25 ToggleHunkDiff,
26};
27
28#[derive(Debug, Clone)]
29pub(super) struct HoveredHunk {
30 pub multi_buffer_range: Range<Anchor>,
31 pub status: DiffHunkStatus,
32 pub diff_base_byte_range: Range<usize>,
33}
34
35#[derive(Debug, Default)]
36pub(super) struct ExpandedHunks {
37 pub(crate) hunks: Vec<ExpandedHunk>,
38 diff_base: HashMap<BufferId, DiffBaseBuffer>,
39 hunk_update_tasks: HashMap<Option<BufferId>, Task<()>>,
40 expand_all: bool,
41}
42
43#[derive(Debug, Clone)]
44pub(super) struct ExpandedHunk {
45 pub blocks: Vec<CustomBlockId>,
46 pub hunk_range: Range<Anchor>,
47 pub diff_base_byte_range: Range<usize>,
48 pub status: DiffHunkStatus,
49 pub folded: bool,
50}
51
52#[derive(Debug)]
53struct DiffBaseBuffer {
54 buffer: Model<Buffer>,
55 diff_base_version: usize,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum DisplayDiffHunk {
60 Folded {
61 display_row: DisplayRow,
62 },
63 Unfolded {
64 diff_base_byte_range: Range<usize>,
65 display_row_range: Range<DisplayRow>,
66 multi_buffer_range: Range<Anchor>,
67 status: DiffHunkStatus,
68 },
69}
70
71impl ExpandedHunks {
72 pub fn hunks(&self, include_folded: bool) -> impl Iterator<Item = &ExpandedHunk> {
73 self.hunks
74 .iter()
75 .filter(move |hunk| include_folded || !hunk.folded)
76 }
77}
78
79impl Editor {
80 pub fn set_expand_all_diff_hunks(&mut self) {
81 self.expanded_hunks.expand_all = true;
82 }
83
84 pub(super) fn toggle_hovered_hunk(
85 &mut self,
86 hovered_hunk: &HoveredHunk,
87 cx: &mut ViewContext<Editor>,
88 ) {
89 let editor_snapshot = self.snapshot(cx);
90 if let Some(diff_hunk) = to_diff_hunk(hovered_hunk, &editor_snapshot.buffer_snapshot) {
91 self.toggle_hunks_expanded(vec![diff_hunk], cx);
92 self.change_selections(None, cx, |selections| selections.refresh());
93 }
94 }
95
96 pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext<Self>) {
97 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
98 let selections = self.selections.disjoint_anchors();
99 self.toggle_hunks_expanded(
100 hunks_for_selections(&multi_buffer_snapshot, &selections),
101 cx,
102 );
103 }
104
105 pub fn expand_all_hunk_diffs(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext<Self>) {
106 let snapshot = self.snapshot(cx);
107 let display_rows_with_expanded_hunks = self
108 .expanded_hunks
109 .hunks(false)
110 .map(|hunk| &hunk.hunk_range)
111 .map(|anchor_range| {
112 (
113 anchor_range
114 .start
115 .to_display_point(&snapshot.display_snapshot)
116 .row(),
117 anchor_range
118 .end
119 .to_display_point(&snapshot.display_snapshot)
120 .row(),
121 )
122 })
123 .collect::<HashMap<_, _>>();
124 let hunks = snapshot
125 .display_snapshot
126 .buffer_snapshot
127 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
128 .filter(|hunk| {
129 let hunk_display_row_range = Point::new(hunk.row_range.start.0, 0)
130 .to_display_point(&snapshot.display_snapshot)
131 ..Point::new(hunk.row_range.end.0, 0)
132 .to_display_point(&snapshot.display_snapshot);
133 let row_range_end =
134 display_rows_with_expanded_hunks.get(&hunk_display_row_range.start.row());
135 row_range_end.is_none() || row_range_end != Some(&hunk_display_row_range.end.row())
136 });
137 self.toggle_hunks_expanded(hunks.collect(), cx);
138 }
139
140 fn toggle_hunks_expanded(
141 &mut self,
142 hunks_to_toggle: Vec<MultiBufferDiffHunk>,
143 cx: &mut ViewContext<Self>,
144 ) {
145 if self.expanded_hunks.expand_all {
146 return;
147 }
148
149 let previous_toggle_task = self.expanded_hunks.hunk_update_tasks.remove(&None);
150 let new_toggle_task = cx.spawn(move |editor, mut cx| async move {
151 if let Some(task) = previous_toggle_task {
152 task.await;
153 }
154
155 editor
156 .update(&mut cx, |editor, cx| {
157 let snapshot = editor.snapshot(cx);
158 let mut hunks_to_toggle = hunks_to_toggle.into_iter().fuse().peekable();
159 let mut highlights_to_remove =
160 Vec::with_capacity(editor.expanded_hunks.hunks.len());
161 let mut blocks_to_remove = HashSet::default();
162 let mut hunks_to_expand = Vec::new();
163 editor.expanded_hunks.hunks.retain(|expanded_hunk| {
164 if expanded_hunk.folded {
165 return true;
166 }
167 let expanded_hunk_row_range = expanded_hunk
168 .hunk_range
169 .start
170 .to_display_point(&snapshot)
171 .row()
172 ..expanded_hunk
173 .hunk_range
174 .end
175 .to_display_point(&snapshot)
176 .row();
177 let mut retain = true;
178 while let Some(hunk_to_toggle) = hunks_to_toggle.peek() {
179 match diff_hunk_to_display(hunk_to_toggle, &snapshot) {
180 DisplayDiffHunk::Folded { .. } => {
181 hunks_to_toggle.next();
182 continue;
183 }
184 DisplayDiffHunk::Unfolded {
185 diff_base_byte_range,
186 display_row_range,
187 multi_buffer_range,
188 status,
189 } => {
190 let hunk_to_toggle_row_range = display_row_range;
191 if hunk_to_toggle_row_range.start > expanded_hunk_row_range.end
192 {
193 break;
194 } else if expanded_hunk_row_range == hunk_to_toggle_row_range {
195 highlights_to_remove.push(expanded_hunk.hunk_range.clone());
196 blocks_to_remove
197 .extend(expanded_hunk.blocks.iter().copied());
198 hunks_to_toggle.next();
199 retain = false;
200 break;
201 } else {
202 hunks_to_expand.push(HoveredHunk {
203 status,
204 multi_buffer_range,
205 diff_base_byte_range,
206 });
207 hunks_to_toggle.next();
208 continue;
209 }
210 }
211 }
212 }
213
214 retain
215 });
216 for hunk in hunks_to_toggle {
217 let remaining_hunk_point_range = Point::new(hunk.row_range.start.0, 0)
218 ..Point::new(hunk.row_range.end.0, 0);
219 let hunk_start = snapshot
220 .buffer_snapshot
221 .anchor_before(remaining_hunk_point_range.start);
222 let hunk_end = snapshot
223 .buffer_snapshot
224 .anchor_in_excerpt(hunk_start.excerpt_id, hunk.buffer_range.end)
225 .unwrap();
226 hunks_to_expand.push(HoveredHunk {
227 status: hunk_status(&hunk),
228 multi_buffer_range: hunk_start..hunk_end,
229 diff_base_byte_range: hunk.diff_base_byte_range.clone(),
230 });
231 }
232
233 editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx);
234 editor.remove_blocks(blocks_to_remove, None, cx);
235 for hunk in hunks_to_expand {
236 editor.expand_diff_hunk(None, &hunk, cx);
237 }
238 cx.notify();
239 })
240 .ok();
241 });
242
243 self.expanded_hunks
244 .hunk_update_tasks
245 .insert(None, cx.background_executor().spawn(new_toggle_task));
246 }
247
248 pub(super) fn expand_diff_hunk(
249 &mut self,
250 diff_base_buffer: Option<Model<Buffer>>,
251 hunk: &HoveredHunk,
252 cx: &mut ViewContext<'_, Editor>,
253 ) -> Option<()> {
254 let buffer = self.buffer.clone();
255 let multi_buffer_snapshot = buffer.read(cx).snapshot(cx);
256 let hunk_range = hunk.multi_buffer_range.clone();
257 let (diff_base_buffer, deleted_text_lines) = buffer.update(cx, |buffer, cx| {
258 let buffer = buffer.buffer(hunk_range.start.buffer_id?)?;
259 let diff_base_buffer = diff_base_buffer
260 .or_else(|| self.current_diff_base_buffer(&buffer, cx))
261 .or_else(|| create_diff_base_buffer(&buffer, cx))?;
262 let deleted_text_lines = buffer.read(cx).diff_base().map(|diff_base| {
263 let diff_start_row = diff_base
264 .offset_to_point(hunk.diff_base_byte_range.start)
265 .row;
266 let diff_end_row = diff_base.offset_to_point(hunk.diff_base_byte_range.end).row;
267 diff_end_row - diff_start_row
268 })?;
269 Some((diff_base_buffer, deleted_text_lines))
270 })?;
271
272 let block_insert_index = match self.expanded_hunks.hunks.binary_search_by(|probe| {
273 probe
274 .hunk_range
275 .start
276 .cmp(&hunk_range.start, &multi_buffer_snapshot)
277 }) {
278 Ok(_already_present) => return None,
279 Err(ix) => ix,
280 };
281
282 let blocks;
283 match hunk.status {
284 DiffHunkStatus::Removed => {
285 blocks = self.insert_blocks(
286 [
287 self.hunk_header_block(&hunk, cx),
288 Self::deleted_text_block(hunk, diff_base_buffer, deleted_text_lines, cx),
289 ],
290 None,
291 cx,
292 );
293 }
294 DiffHunkStatus::Added => {
295 self.highlight_rows::<DiffRowHighlight>(
296 hunk_range.clone(),
297 added_hunk_color(cx),
298 false,
299 cx,
300 );
301 blocks = self.insert_blocks([self.hunk_header_block(&hunk, cx)], None, cx);
302 }
303 DiffHunkStatus::Modified => {
304 self.highlight_rows::<DiffRowHighlight>(
305 hunk_range.clone(),
306 added_hunk_color(cx),
307 false,
308 cx,
309 );
310 blocks = self.insert_blocks(
311 [
312 self.hunk_header_block(&hunk, cx),
313 Self::deleted_text_block(hunk, diff_base_buffer, deleted_text_lines, cx),
314 ],
315 None,
316 cx,
317 );
318 }
319 };
320 self.expanded_hunks.hunks.insert(
321 block_insert_index,
322 ExpandedHunk {
323 blocks,
324 hunk_range,
325 status: hunk.status,
326 folded: false,
327 diff_base_byte_range: hunk.diff_base_byte_range.clone(),
328 },
329 );
330
331 Some(())
332 }
333
334 fn apply_diff_hunks_in_range(
335 &mut self,
336 range: Range<Anchor>,
337 cx: &mut ViewContext<'_, Editor>,
338 ) -> Option<()> {
339 let (buffer, range, _) = self
340 .buffer
341 .read(cx)
342 .range_to_buffer_ranges(range, cx)
343 .into_iter()
344 .next()?;
345
346 buffer.update(cx, |branch_buffer, cx| {
347 branch_buffer.merge_into_base(vec![range], cx);
348 });
349
350 if let Some(project) = self.project.clone() {
351 self.save(true, project, cx).detach_and_log_err(cx);
352 }
353
354 None
355 }
356
357 pub(crate) fn apply_all_diff_hunks(
358 &mut self,
359 _: &ApplyAllDiffHunks,
360 cx: &mut ViewContext<Self>,
361 ) {
362 let buffers = self.buffer.read(cx).all_buffers();
363 for branch_buffer in buffers {
364 branch_buffer.update(cx, |branch_buffer, cx| {
365 branch_buffer.merge_into_base(Vec::new(), cx);
366 });
367 }
368
369 if let Some(project) = self.project.clone() {
370 self.save(true, project, cx).detach_and_log_err(cx);
371 }
372 }
373
374 pub(crate) fn apply_selected_diff_hunks(
375 &mut self,
376 _: &ApplySelectedDiffHunks,
377 cx: &mut ViewContext<Self>,
378 ) {
379 let snapshot = self.buffer.read(cx).snapshot(cx);
380 let hunks = hunks_for_selections(&snapshot, &self.selections.disjoint_anchors());
381
382 self.transact(cx, |editor, cx| {
383 if hunks.is_empty() {
384 // If there are no selected hunks, e.g. because we're using the keybinding with nothing selected, apply the first hunk.
385 if let Some(first_hunk) = editor.expanded_hunks.hunks.first() {
386 editor.apply_diff_hunks_in_range(first_hunk.hunk_range.clone(), cx);
387 }
388 } else {
389 let mut ranges_by_buffer = HashMap::default();
390
391 for hunk in hunks {
392 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
393 ranges_by_buffer
394 .entry(buffer.clone())
395 .or_insert_with(Vec::new)
396 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
397 }
398 }
399
400 for (buffer, ranges) in ranges_by_buffer {
401 buffer.update(cx, |buffer, cx| {
402 buffer.merge_into_base(ranges, cx);
403 });
404 }
405 }
406 });
407
408 if let Some(project) = self.project.clone() {
409 self.save(true, project, cx).detach_and_log_err(cx);
410 }
411 }
412
413 fn hunk_header_block(
414 &self,
415 hunk: &HoveredHunk,
416 cx: &mut ViewContext<'_, Editor>,
417 ) -> BlockProperties<Anchor> {
418 let is_branch_buffer = self
419 .buffer
420 .read(cx)
421 .point_to_buffer_offset(hunk.multi_buffer_range.start, cx)
422 .map_or(false, |(buffer, _, _)| {
423 buffer.read(cx).diff_base_buffer().is_some()
424 });
425
426 BlockProperties {
427 placement: BlockPlacement::Above(hunk.multi_buffer_range.start),
428 height: 0,
429 style: BlockStyle::Sticky,
430 priority: 1,
431 render: Arc::new({
432 let editor = cx.view().clone();
433 let hunk = hunk.clone();
434
435 move |cx| {
436 let is_hunk_selected = editor.update(&mut **cx, |editor, cx| {
437 let snapshot = editor.buffer.read(cx).snapshot(cx);
438 let selections = &editor.selections.all::<Point>(cx);
439
440 if editor.focus_handle(cx).is_focused(cx) && !selections.is_empty() {
441 if let Some(hunk) = to_diff_hunk(&hunk, &snapshot) {
442 is_hunk_selected(&hunk, selections)
443 } else {
444 false
445 }
446 } else {
447 // If we have no cursor, or aren't focused, then default to the first hunk
448 // because that's what the keyboard shortcuts do.
449 editor
450 .expanded_hunks
451 .hunks
452 .first()
453 .map(|first_hunk| first_hunk.hunk_range == hunk.multi_buffer_range)
454 .unwrap_or(false)
455 }
456 });
457
458 let focus_handle = editor.focus_handle(cx);
459
460 let handle_discard_click = {
461 let editor = editor.clone();
462 let hunk = hunk.clone();
463 move |_event: &ClickEvent, cx: &mut WindowContext| {
464 let multi_buffer = editor.read(cx).buffer().clone();
465 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
466 let mut revert_changes = HashMap::default();
467 if let Some(hunk) =
468 crate::hunk_diff::to_diff_hunk(&hunk, &multi_buffer_snapshot)
469 {
470 Editor::prepare_revert_change(
471 &mut revert_changes,
472 &multi_buffer,
473 &hunk,
474 cx,
475 );
476 }
477 if !revert_changes.is_empty() {
478 editor.update(cx, |editor, cx| editor.revert(revert_changes, cx));
479 }
480 }
481 };
482
483 let handle_apply_click = {
484 let editor = editor.clone();
485 let hunk = hunk.clone();
486 move |_event: &ClickEvent, cx: &mut WindowContext| {
487 editor.update(cx, |editor, cx| {
488 editor
489 .apply_diff_hunks_in_range(hunk.multi_buffer_range.clone(), cx);
490 });
491 }
492 };
493
494 let discard_key_binding =
495 KeyBinding::for_action_in(&RevertSelectedHunks, &focus_handle, cx);
496
497 let discard_tooltip = {
498 let focus_handle = editor.focus_handle(cx);
499 move |cx: &mut WindowContext| {
500 Tooltip::for_action_in(
501 "Discard Hunk",
502 &RevertSelectedHunks,
503 &focus_handle,
504 cx,
505 )
506 }
507 };
508
509 h_flex()
510 .id(cx.block_id)
511 .pr_5()
512 .w_full()
513 .justify_end()
514 .child(
515 h_flex()
516 .h(cx.line_height())
517 .gap_1()
518 .px_1()
519 .pb_1()
520 .border_x_1()
521 .border_b_1()
522 .border_color(cx.theme().colors().border_variant)
523 .rounded_b_lg()
524 .bg(cx.theme().colors().editor_background)
525 .shadow(smallvec::smallvec![gpui::BoxShadow {
526 color: gpui::hsla(0.0, 0.0, 0.0, 0.1),
527 blur_radius: px(1.0),
528 spread_radius: px(1.0),
529 offset: gpui::point(px(0.), px(1.0)),
530 }])
531 .when(!is_branch_buffer, |row| {
532 row.child(
533 IconButton::new("next-hunk", IconName::ArrowDown)
534 .shape(IconButtonShape::Square)
535 .icon_size(IconSize::Small)
536 .tooltip({
537 let focus_handle = editor.focus_handle(cx);
538 move |cx| {
539 Tooltip::for_action_in(
540 "Next Hunk",
541 &GoToHunk,
542 &focus_handle.clone(),
543 cx,
544 )
545 }
546 })
547 .on_click({
548 let editor = editor.clone();
549 let hunk = hunk.clone();
550 move |_event, cx| {
551 editor.update(cx, |editor, cx| {
552 editor.go_to_subsequent_hunk(
553 hunk.multi_buffer_range.end,
554 cx,
555 );
556 });
557 }
558 }),
559 )
560 .child(
561 IconButton::new("prev-hunk", IconName::ArrowUp)
562 .shape(IconButtonShape::Square)
563 .icon_size(IconSize::Small)
564 .tooltip({
565 let focus_handle = editor.focus_handle(cx);
566 move |cx| {
567 Tooltip::for_action_in(
568 "Previous Hunk",
569 &GoToPrevHunk,
570 &focus_handle,
571 cx,
572 )
573 }
574 })
575 .on_click({
576 let editor = editor.clone();
577 let hunk = hunk.clone();
578 move |_event, cx| {
579 editor.update(cx, |editor, cx| {
580 editor.go_to_preceding_hunk(
581 hunk.multi_buffer_range.start,
582 cx,
583 );
584 });
585 }
586 }),
587 )
588 })
589 .child(if is_branch_buffer {
590 if is_hunk_selected {
591 Button::new("discard", "Discard")
592 .style(ButtonStyle::Tinted(TintColor::Negative))
593 .label_size(LabelSize::Small)
594 .key_binding(discard_key_binding)
595 .on_click(handle_discard_click.clone())
596 .into_any_element()
597 } else {
598 IconButton::new("discard", IconName::Close)
599 .style(ButtonStyle::Tinted(TintColor::Negative))
600 .shape(IconButtonShape::Square)
601 .icon_size(IconSize::XSmall)
602 .tooltip(discard_tooltip.clone())
603 .on_click(handle_discard_click.clone())
604 .into_any_element()
605 }
606 } else {
607 if is_hunk_selected {
608 Button::new("undo", "Undo")
609 .style(ButtonStyle::Tinted(TintColor::Negative))
610 .label_size(LabelSize::Small)
611 .key_binding(discard_key_binding)
612 .on_click(handle_discard_click.clone())
613 .into_any_element()
614 } else {
615 IconButton::new("undo", IconName::Undo)
616 .shape(IconButtonShape::Square)
617 .icon_size(IconSize::Small)
618 .tooltip(discard_tooltip.clone())
619 .on_click(handle_discard_click.clone())
620 .into_any_element()
621 }
622 })
623 .when(is_branch_buffer, |this| {
624 this.child({
625 let button = Button::new("apply", "Apply")
626 .style(ButtonStyle::Tinted(TintColor::Positive))
627 .label_size(LabelSize::Small)
628 .key_binding(KeyBinding::for_action_in(
629 &ApplySelectedDiffHunks,
630 &focus_handle,
631 cx,
632 ))
633 .on_click(handle_apply_click.clone())
634 .into_any_element();
635 if is_hunk_selected {
636 button
637 } else {
638 IconButton::new("apply", IconName::Check)
639 .style(ButtonStyle::Tinted(TintColor::Positive))
640 .shape(IconButtonShape::Square)
641 .icon_size(IconSize::XSmall)
642 .tooltip({
643 let focus_handle = editor.focus_handle(cx);
644 move |cx| {
645 Tooltip::for_action_in(
646 "Apply Hunk",
647 &ApplySelectedDiffHunks,
648 &focus_handle,
649 cx,
650 )
651 }
652 })
653 .on_click(handle_apply_click.clone())
654 .into_any_element()
655 }
656 })
657 })
658 .when(!is_branch_buffer, |div| {
659 div.child(
660 IconButton::new("collapse", IconName::Close)
661 .shape(IconButtonShape::Square)
662 .icon_size(IconSize::Small)
663 .tooltip({
664 let focus_handle = editor.focus_handle(cx);
665 move |cx| {
666 Tooltip::for_action_in(
667 "Collapse Hunk",
668 &ToggleHunkDiff,
669 &focus_handle,
670 cx,
671 )
672 }
673 })
674 .on_click({
675 let editor = editor.clone();
676 let hunk = hunk.clone();
677 move |_event, cx| {
678 editor.update(cx, |editor, cx| {
679 editor.toggle_hovered_hunk(&hunk, cx);
680 });
681 }
682 }),
683 )
684 }),
685 )
686 .into_any_element()
687 }
688 }),
689 }
690 }
691
692 fn deleted_text_block(
693 hunk: &HoveredHunk,
694 diff_base_buffer: Model<Buffer>,
695 deleted_text_height: u32,
696 cx: &mut ViewContext<'_, Editor>,
697 ) -> BlockProperties<Anchor> {
698 let gutter_color = match hunk.status {
699 DiffHunkStatus::Added => unreachable!(),
700 DiffHunkStatus::Modified => cx.theme().status().modified,
701 DiffHunkStatus::Removed => cx.theme().status().deleted,
702 };
703 let deleted_hunk_color = deleted_hunk_color(cx);
704 let (editor_height, editor_with_deleted_text) =
705 editor_with_deleted_text(diff_base_buffer, deleted_hunk_color, hunk, cx);
706 let editor = cx.view().clone();
707 let hunk = hunk.clone();
708 let height = editor_height.max(deleted_text_height);
709 BlockProperties {
710 placement: BlockPlacement::Above(hunk.multi_buffer_range.start),
711 height,
712 style: BlockStyle::Flex,
713 priority: 1,
714 render: Arc::new(move |cx| {
715 let width = EditorElement::diff_hunk_strip_width(cx.line_height());
716 let gutter_dimensions = editor.read(cx.context).gutter_dimensions;
717
718 h_flex()
719 .id(cx.block_id)
720 .block_mouse_down()
721 .bg(deleted_hunk_color)
722 .h(height as f32 * cx.line_height())
723 .w_full()
724 .child(
725 h_flex()
726 .id("gutter")
727 .max_w(gutter_dimensions.full_width())
728 .min_w(gutter_dimensions.full_width())
729 .size_full()
730 .child(
731 h_flex()
732 .id("gutter hunk")
733 .bg(gutter_color)
734 .pl(gutter_dimensions.margin
735 + gutter_dimensions
736 .git_blame_entries_width
737 .unwrap_or_default())
738 .max_w(width)
739 .min_w(width)
740 .size_full()
741 .cursor(CursorStyle::PointingHand)
742 .on_mouse_down(MouseButton::Left, {
743 let editor = editor.clone();
744 let hunk = hunk.clone();
745 move |_event, cx| {
746 editor.update(cx, |editor, cx| {
747 editor.toggle_hovered_hunk(&hunk, cx);
748 });
749 }
750 }),
751 ),
752 )
753 .child(editor_with_deleted_text.clone())
754 .into_any_element()
755 }),
756 }
757 }
758
759 pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) -> bool {
760 if self.expanded_hunks.expand_all {
761 return false;
762 }
763 self.expanded_hunks.hunk_update_tasks.clear();
764 self.clear_row_highlights::<DiffRowHighlight>();
765 let to_remove = self
766 .expanded_hunks
767 .hunks
768 .drain(..)
769 .flat_map(|expanded_hunk| expanded_hunk.blocks.into_iter())
770 .collect::<HashSet<_>>();
771 if to_remove.is_empty() {
772 false
773 } else {
774 self.remove_blocks(to_remove, None, cx);
775 true
776 }
777 }
778
779 pub(super) fn sync_expanded_diff_hunks(
780 &mut self,
781 buffer: Model<Buffer>,
782 cx: &mut ViewContext<'_, Self>,
783 ) {
784 let buffer_id = buffer.read(cx).remote_id();
785 let buffer_diff_base_version = buffer.read(cx).diff_base_version();
786 self.expanded_hunks
787 .hunk_update_tasks
788 .remove(&Some(buffer_id));
789 let diff_base_buffer = self.current_diff_base_buffer(&buffer, cx);
790 let new_sync_task = cx.spawn(move |editor, mut cx| async move {
791 let diff_base_buffer_unchanged = diff_base_buffer.is_some();
792 let Ok(diff_base_buffer) =
793 cx.update(|cx| diff_base_buffer.or_else(|| create_diff_base_buffer(&buffer, cx)))
794 else {
795 return;
796 };
797 editor
798 .update(&mut cx, |editor, cx| {
799 if let Some(diff_base_buffer) = &diff_base_buffer {
800 editor.expanded_hunks.diff_base.insert(
801 buffer_id,
802 DiffBaseBuffer {
803 buffer: diff_base_buffer.clone(),
804 diff_base_version: buffer_diff_base_version,
805 },
806 );
807 }
808
809 let snapshot = editor.snapshot(cx);
810 let mut recalculated_hunks = snapshot
811 .buffer_snapshot
812 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
813 .filter(|hunk| hunk.buffer_id == buffer_id)
814 .fuse()
815 .peekable();
816 let mut highlights_to_remove =
817 Vec::with_capacity(editor.expanded_hunks.hunks.len());
818 let mut blocks_to_remove = HashSet::default();
819 let mut hunks_to_reexpand =
820 Vec::with_capacity(editor.expanded_hunks.hunks.len());
821 editor.expanded_hunks.hunks.retain_mut(|expanded_hunk| {
822 if expanded_hunk.hunk_range.start.buffer_id != Some(buffer_id) {
823 return true;
824 };
825
826 let mut retain = false;
827 if diff_base_buffer_unchanged {
828 let expanded_hunk_display_range = expanded_hunk
829 .hunk_range
830 .start
831 .to_display_point(&snapshot)
832 .row()
833 ..expanded_hunk
834 .hunk_range
835 .end
836 .to_display_point(&snapshot)
837 .row();
838 while let Some(buffer_hunk) = recalculated_hunks.peek() {
839 match diff_hunk_to_display(buffer_hunk, &snapshot) {
840 DisplayDiffHunk::Folded { display_row } => {
841 recalculated_hunks.next();
842 if !expanded_hunk.folded
843 && expanded_hunk_display_range
844 .to_inclusive()
845 .contains(&display_row)
846 {
847 retain = true;
848 expanded_hunk.folded = true;
849 highlights_to_remove
850 .push(expanded_hunk.hunk_range.clone());
851 for block in expanded_hunk.blocks.drain(..) {
852 blocks_to_remove.insert(block);
853 }
854 break;
855 } else {
856 continue;
857 }
858 }
859 DisplayDiffHunk::Unfolded {
860 diff_base_byte_range,
861 display_row_range,
862 multi_buffer_range,
863 status,
864 } => {
865 let hunk_display_range = display_row_range;
866
867 if expanded_hunk_display_range.start
868 > hunk_display_range.end
869 {
870 recalculated_hunks.next();
871 if editor.expanded_hunks.expand_all {
872 hunks_to_reexpand.push(HoveredHunk {
873 status,
874 multi_buffer_range,
875 diff_base_byte_range,
876 });
877 }
878 continue;
879 }
880
881 if expanded_hunk_display_range.end
882 < hunk_display_range.start
883 {
884 break;
885 }
886
887 if !expanded_hunk.folded
888 && expanded_hunk_display_range == hunk_display_range
889 && expanded_hunk.status == hunk_status(buffer_hunk)
890 && expanded_hunk.diff_base_byte_range
891 == buffer_hunk.diff_base_byte_range
892 {
893 recalculated_hunks.next();
894 retain = true;
895 } else {
896 hunks_to_reexpand.push(HoveredHunk {
897 status,
898 multi_buffer_range,
899 diff_base_byte_range,
900 });
901 }
902 break;
903 }
904 }
905 }
906 }
907 if !retain {
908 blocks_to_remove.extend(expanded_hunk.blocks.drain(..));
909 highlights_to_remove.push(expanded_hunk.hunk_range.clone());
910 }
911 retain
912 });
913
914 if editor.expanded_hunks.expand_all {
915 for hunk in recalculated_hunks {
916 match diff_hunk_to_display(&hunk, &snapshot) {
917 DisplayDiffHunk::Folded { .. } => {}
918 DisplayDiffHunk::Unfolded {
919 diff_base_byte_range,
920 multi_buffer_range,
921 status,
922 ..
923 } => {
924 hunks_to_reexpand.push(HoveredHunk {
925 status,
926 multi_buffer_range,
927 diff_base_byte_range,
928 });
929 }
930 }
931 }
932 }
933
934 editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx);
935 editor.remove_blocks(blocks_to_remove, None, cx);
936
937 if let Some(diff_base_buffer) = &diff_base_buffer {
938 for hunk in hunks_to_reexpand {
939 editor.expand_diff_hunk(Some(diff_base_buffer.clone()), &hunk, cx);
940 }
941 }
942 })
943 .ok();
944 });
945
946 self.expanded_hunks.hunk_update_tasks.insert(
947 Some(buffer_id),
948 cx.background_executor().spawn(new_sync_task),
949 );
950 }
951
952 fn current_diff_base_buffer(
953 &mut self,
954 buffer: &Model<Buffer>,
955 cx: &mut AppContext,
956 ) -> Option<Model<Buffer>> {
957 buffer.update(cx, |buffer, _| {
958 match self.expanded_hunks.diff_base.entry(buffer.remote_id()) {
959 hash_map::Entry::Occupied(o) => {
960 if o.get().diff_base_version != buffer.diff_base_version() {
961 o.remove();
962 None
963 } else {
964 Some(o.get().buffer.clone())
965 }
966 }
967 hash_map::Entry::Vacant(_) => None,
968 }
969 })
970 }
971
972 fn go_to_subsequent_hunk(&mut self, position: Anchor, cx: &mut ViewContext<Self>) {
973 let snapshot = self.snapshot(cx);
974 let position = position.to_point(&snapshot.buffer_snapshot);
975 if let Some(hunk) = self.go_to_hunk_after_position(&snapshot, position, cx) {
976 let multi_buffer_start = snapshot
977 .buffer_snapshot
978 .anchor_before(Point::new(hunk.row_range.start.0, 0));
979 let multi_buffer_end = snapshot
980 .buffer_snapshot
981 .anchor_after(Point::new(hunk.row_range.end.0, 0));
982 self.expand_diff_hunk(
983 None,
984 &HoveredHunk {
985 multi_buffer_range: multi_buffer_start..multi_buffer_end,
986 status: hunk_status(&hunk),
987 diff_base_byte_range: hunk.diff_base_byte_range,
988 },
989 cx,
990 );
991 }
992 }
993
994 fn go_to_preceding_hunk(&mut self, position: Anchor, cx: &mut ViewContext<Self>) {
995 let snapshot = self.snapshot(cx);
996 let position = position.to_point(&snapshot.buffer_snapshot);
997 let hunk = self.go_to_hunk_before_position(&snapshot, position, cx);
998 if let Some(hunk) = hunk {
999 let multi_buffer_start = snapshot
1000 .buffer_snapshot
1001 .anchor_before(Point::new(hunk.row_range.start.0, 0));
1002 let multi_buffer_end = snapshot
1003 .buffer_snapshot
1004 .anchor_after(Point::new(hunk.row_range.end.0, 0));
1005 self.expand_diff_hunk(
1006 None,
1007 &HoveredHunk {
1008 multi_buffer_range: multi_buffer_start..multi_buffer_end,
1009 status: hunk_status(&hunk),
1010 diff_base_byte_range: hunk.diff_base_byte_range,
1011 },
1012 cx,
1013 );
1014 }
1015 }
1016}
1017
1018fn to_diff_hunk(
1019 hovered_hunk: &HoveredHunk,
1020 multi_buffer_snapshot: &MultiBufferSnapshot,
1021) -> Option<MultiBufferDiffHunk> {
1022 let buffer_id = hovered_hunk
1023 .multi_buffer_range
1024 .start
1025 .buffer_id
1026 .or(hovered_hunk.multi_buffer_range.end.buffer_id)?;
1027 let buffer_range = hovered_hunk.multi_buffer_range.start.text_anchor
1028 ..hovered_hunk.multi_buffer_range.end.text_anchor;
1029 let point_range = hovered_hunk
1030 .multi_buffer_range
1031 .to_point(multi_buffer_snapshot);
1032 Some(MultiBufferDiffHunk {
1033 row_range: MultiBufferRow(point_range.start.row)..MultiBufferRow(point_range.end.row),
1034 buffer_id,
1035 buffer_range,
1036 diff_base_byte_range: hovered_hunk.diff_base_byte_range.clone(),
1037 })
1038}
1039
1040fn create_diff_base_buffer(buffer: &Model<Buffer>, cx: &mut AppContext) -> Option<Model<Buffer>> {
1041 buffer
1042 .update(cx, |buffer, _| {
1043 let language = buffer.language().cloned();
1044 let diff_base = buffer.diff_base()?.clone();
1045 Some((buffer.line_ending(), diff_base, language))
1046 })
1047 .map(|(line_ending, diff_base, language)| {
1048 cx.new_model(|cx| {
1049 let buffer = Buffer::local_normalized(diff_base, line_ending, cx);
1050 match language {
1051 Some(language) => buffer.with_language(language, cx),
1052 None => buffer,
1053 }
1054 })
1055 })
1056}
1057
1058fn added_hunk_color(cx: &AppContext) -> Hsla {
1059 let mut created_color = cx.theme().status().git().created;
1060 created_color.fade_out(0.7);
1061 created_color
1062}
1063
1064fn deleted_hunk_color(cx: &AppContext) -> Hsla {
1065 let mut deleted_color = cx.theme().status().deleted;
1066 deleted_color.fade_out(0.7);
1067 deleted_color
1068}
1069
1070fn editor_with_deleted_text(
1071 diff_base_buffer: Model<Buffer>,
1072 deleted_color: Hsla,
1073 hunk: &HoveredHunk,
1074 cx: &mut ViewContext<'_, Editor>,
1075) -> (u32, View<Editor>) {
1076 let parent_editor = cx.view().downgrade();
1077 let editor = cx.new_view(|cx| {
1078 let multi_buffer =
1079 cx.new_model(|_| MultiBuffer::without_headers(language::Capability::ReadOnly));
1080 multi_buffer.update(cx, |multi_buffer, cx| {
1081 multi_buffer.push_excerpts(
1082 diff_base_buffer,
1083 Some(ExcerptRange {
1084 context: hunk.diff_base_byte_range.clone(),
1085 primary: None,
1086 }),
1087 cx,
1088 );
1089 });
1090
1091 let mut editor = Editor::for_multibuffer(multi_buffer, None, true, cx);
1092 editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
1093 editor.set_show_wrap_guides(false, cx);
1094 editor.set_show_gutter(false, cx);
1095 editor.scroll_manager.set_forbid_vertical_scroll(true);
1096 editor.set_read_only(true);
1097 editor.set_show_inline_completions(Some(false), cx);
1098
1099 enum DeletedBlockRowHighlight {}
1100 editor.highlight_rows::<DeletedBlockRowHighlight>(
1101 Anchor::min()..Anchor::max(),
1102 deleted_color,
1103 false,
1104 cx,
1105 );
1106 editor.set_current_line_highlight(Some(CurrentLineHighlight::None)); //
1107 editor
1108 ._subscriptions
1109 .extend([cx.on_blur(&editor.focus_handle, |editor, cx| {
1110 editor.change_selections(None, cx, |s| {
1111 s.try_cancel();
1112 });
1113 })]);
1114
1115 let original_multi_buffer_range = hunk.multi_buffer_range.clone();
1116 let diff_base_range = hunk.diff_base_byte_range.clone();
1117 editor
1118 .register_action::<RevertSelectedHunks>({
1119 let parent_editor = parent_editor.clone();
1120 move |_, cx| {
1121 parent_editor
1122 .update(cx, |editor, cx| {
1123 let Some((buffer, original_text)) =
1124 editor.buffer().update(cx, |buffer, cx| {
1125 let (_, buffer, _) = buffer.excerpt_containing(
1126 original_multi_buffer_range.start,
1127 cx,
1128 )?;
1129 let original_text =
1130 buffer.read(cx).diff_base()?.slice(diff_base_range.clone());
1131 Some((buffer, Arc::from(original_text.to_string())))
1132 })
1133 else {
1134 return;
1135 };
1136 buffer.update(cx, |buffer, cx| {
1137 buffer.edit(
1138 Some((
1139 original_multi_buffer_range.start.text_anchor
1140 ..original_multi_buffer_range.end.text_anchor,
1141 original_text,
1142 )),
1143 None,
1144 cx,
1145 )
1146 });
1147 })
1148 .ok();
1149 }
1150 })
1151 .detach();
1152 let hunk = hunk.clone();
1153 editor
1154 .register_action::<ToggleHunkDiff>(move |_, cx| {
1155 parent_editor
1156 .update(cx, |editor, cx| {
1157 editor.toggle_hovered_hunk(&hunk, cx);
1158 })
1159 .ok();
1160 })
1161 .detach();
1162 editor
1163 });
1164
1165 let editor_height = editor.update(cx, |editor, cx| editor.max_point(cx).row().0);
1166 (editor_height, editor)
1167}
1168
1169impl DisplayDiffHunk {
1170 pub fn start_display_row(&self) -> DisplayRow {
1171 match self {
1172 &DisplayDiffHunk::Folded { display_row } => display_row,
1173 DisplayDiffHunk::Unfolded {
1174 display_row_range, ..
1175 } => display_row_range.start,
1176 }
1177 }
1178
1179 pub fn contains_display_row(&self, display_row: DisplayRow) -> bool {
1180 let range = match self {
1181 &DisplayDiffHunk::Folded { display_row } => display_row..=display_row,
1182
1183 DisplayDiffHunk::Unfolded {
1184 display_row_range, ..
1185 } => display_row_range.start..=display_row_range.end,
1186 };
1187
1188 range.contains(&display_row)
1189 }
1190}
1191
1192pub fn diff_hunk_to_display(
1193 hunk: &MultiBufferDiffHunk,
1194 snapshot: &DisplaySnapshot,
1195) -> DisplayDiffHunk {
1196 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
1197 let hunk_start_point_sub = Point::new(hunk.row_range.start.0.saturating_sub(1), 0);
1198 let hunk_end_point_sub = Point::new(
1199 hunk.row_range
1200 .end
1201 .0
1202 .saturating_sub(1)
1203 .max(hunk.row_range.start.0),
1204 0,
1205 );
1206
1207 let status = hunk_status(hunk);
1208 let is_removal = status == DiffHunkStatus::Removed;
1209
1210 let folds_start = Point::new(hunk.row_range.start.0.saturating_sub(2), 0);
1211 let folds_end = Point::new(hunk.row_range.end.0 + 2, 0);
1212 let folds_range = folds_start..folds_end;
1213
1214 let containing_fold = snapshot.folds_in_range(folds_range).find(|fold| {
1215 let fold_point_range = fold.range.to_point(&snapshot.buffer_snapshot);
1216 let fold_point_range = fold_point_range.start..=fold_point_range.end;
1217
1218 let folded_start = fold_point_range.contains(&hunk_start_point);
1219 let folded_end = fold_point_range.contains(&hunk_end_point_sub);
1220 let folded_start_sub = fold_point_range.contains(&hunk_start_point_sub);
1221
1222 (folded_start && folded_end) || (is_removal && folded_start_sub)
1223 });
1224
1225 if let Some(fold) = containing_fold {
1226 let row = fold.range.start.to_display_point(snapshot).row();
1227 DisplayDiffHunk::Folded { display_row: row }
1228 } else {
1229 let start = hunk_start_point.to_display_point(snapshot).row();
1230
1231 let hunk_end_row = hunk.row_range.end.max(hunk.row_range.start);
1232 let hunk_end_point = Point::new(hunk_end_row.0, 0);
1233
1234 let multi_buffer_start = snapshot.buffer_snapshot.anchor_before(hunk_start_point);
1235 let multi_buffer_end = snapshot
1236 .buffer_snapshot
1237 .anchor_in_excerpt(multi_buffer_start.excerpt_id, hunk.buffer_range.end)
1238 .unwrap();
1239 let end = hunk_end_point.to_display_point(snapshot).row();
1240
1241 DisplayDiffHunk::Unfolded {
1242 display_row_range: start..end,
1243 multi_buffer_range: multi_buffer_start..multi_buffer_end,
1244 status,
1245 diff_base_byte_range: hunk.diff_base_byte_range.clone(),
1246 }
1247 }
1248}
1249
1250#[cfg(test)]
1251mod tests {
1252 use super::*;
1253 use crate::{editor_tests::init_test, hunk_status};
1254 use gpui::{Context, TestAppContext};
1255 use language::Capability::ReadWrite;
1256 use multi_buffer::{ExcerptRange, MultiBuffer, MultiBufferRow};
1257 use project::{FakeFs, Project};
1258 use unindent::Unindent as _;
1259
1260 #[gpui::test]
1261 async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
1262 use git::diff::DiffHunkStatus;
1263 init_test(cx, |_| {});
1264
1265 let fs = FakeFs::new(cx.background_executor.clone());
1266 let project = Project::test(fs, [], cx).await;
1267
1268 // buffer has two modified hunks with two rows each
1269 let buffer_1 = project.update(cx, |project, cx| {
1270 project.create_local_buffer(
1271 "
1272 1.zero
1273 1.ONE
1274 1.TWO
1275 1.three
1276 1.FOUR
1277 1.FIVE
1278 1.six
1279 "
1280 .unindent()
1281 .as_str(),
1282 None,
1283 cx,
1284 )
1285 });
1286 buffer_1.update(cx, |buffer, cx| {
1287 buffer.set_diff_base(
1288 Some(
1289 "
1290 1.zero
1291 1.one
1292 1.two
1293 1.three
1294 1.four
1295 1.five
1296 1.six
1297 "
1298 .unindent(),
1299 ),
1300 cx,
1301 );
1302 });
1303
1304 // buffer has a deletion hunk and an insertion hunk
1305 let buffer_2 = project.update(cx, |project, cx| {
1306 project.create_local_buffer(
1307 "
1308 2.zero
1309 2.one
1310 2.two
1311 2.three
1312 2.four
1313 2.five
1314 2.six
1315 "
1316 .unindent()
1317 .as_str(),
1318 None,
1319 cx,
1320 )
1321 });
1322 buffer_2.update(cx, |buffer, cx| {
1323 buffer.set_diff_base(
1324 Some(
1325 "
1326 2.zero
1327 2.one
1328 2.one-and-a-half
1329 2.two
1330 2.three
1331 2.four
1332 2.six
1333 "
1334 .unindent(),
1335 ),
1336 cx,
1337 );
1338 });
1339
1340 cx.background_executor.run_until_parked();
1341
1342 let multibuffer = cx.new_model(|cx| {
1343 let mut multibuffer = MultiBuffer::new(ReadWrite);
1344 multibuffer.push_excerpts(
1345 buffer_1.clone(),
1346 [
1347 // excerpt ends in the middle of a modified hunk
1348 ExcerptRange {
1349 context: Point::new(0, 0)..Point::new(1, 5),
1350 primary: Default::default(),
1351 },
1352 // excerpt begins in the middle of a modified hunk
1353 ExcerptRange {
1354 context: Point::new(5, 0)..Point::new(6, 5),
1355 primary: Default::default(),
1356 },
1357 ],
1358 cx,
1359 );
1360 multibuffer.push_excerpts(
1361 buffer_2.clone(),
1362 [
1363 // excerpt ends at a deletion
1364 ExcerptRange {
1365 context: Point::new(0, 0)..Point::new(1, 5),
1366 primary: Default::default(),
1367 },
1368 // excerpt starts at a deletion
1369 ExcerptRange {
1370 context: Point::new(2, 0)..Point::new(2, 5),
1371 primary: Default::default(),
1372 },
1373 // excerpt fully contains a deletion hunk
1374 ExcerptRange {
1375 context: Point::new(1, 0)..Point::new(2, 5),
1376 primary: Default::default(),
1377 },
1378 // excerpt fully contains an insertion hunk
1379 ExcerptRange {
1380 context: Point::new(4, 0)..Point::new(6, 5),
1381 primary: Default::default(),
1382 },
1383 ],
1384 cx,
1385 );
1386 multibuffer
1387 });
1388
1389 let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx));
1390
1391 assert_eq!(
1392 snapshot.text(),
1393 "
1394 1.zero
1395 1.ONE
1396 1.FIVE
1397 1.six
1398 2.zero
1399 2.one
1400 2.two
1401 2.one
1402 2.two
1403 2.four
1404 2.five
1405 2.six"
1406 .unindent()
1407 );
1408
1409 let expected = [
1410 (
1411 DiffHunkStatus::Modified,
1412 MultiBufferRow(1)..MultiBufferRow(2),
1413 ),
1414 (
1415 DiffHunkStatus::Modified,
1416 MultiBufferRow(2)..MultiBufferRow(3),
1417 ),
1418 //TODO: Define better when and where removed hunks show up at range extremities
1419 (
1420 DiffHunkStatus::Removed,
1421 MultiBufferRow(6)..MultiBufferRow(6),
1422 ),
1423 (
1424 DiffHunkStatus::Removed,
1425 MultiBufferRow(8)..MultiBufferRow(8),
1426 ),
1427 (
1428 DiffHunkStatus::Added,
1429 MultiBufferRow(10)..MultiBufferRow(11),
1430 ),
1431 ];
1432
1433 assert_eq!(
1434 snapshot
1435 .git_diff_hunks_in_range(MultiBufferRow(0)..MultiBufferRow(12))
1436 .map(|hunk| (hunk_status(&hunk), hunk.row_range))
1437 .collect::<Vec<_>>(),
1438 &expected,
1439 );
1440
1441 assert_eq!(
1442 snapshot
1443 .git_diff_hunks_in_range_rev(MultiBufferRow(0)..MultiBufferRow(12))
1444 .map(|hunk| (hunk_status(&hunk), hunk.row_range))
1445 .collect::<Vec<_>>(),
1446 expected
1447 .iter()
1448 .rev()
1449 .cloned()
1450 .collect::<Vec<_>>()
1451 .as_slice(),
1452 );
1453 }
1454}