1use crate::{Editor, EditorEvent};
2use collections::HashSet;
3use futures::{channel::mpsc, future::join_all};
4use gpui::{AppContext, EventEmitter, FocusableView, Model, Render, Subscription, Task, View};
5use language::{Buffer, BufferEvent, Capability};
6use multi_buffer::{ExcerptRange, MultiBuffer};
7use project::Project;
8use smol::stream::StreamExt;
9use std::{ops::Range, time::Duration};
10use text::ToOffset;
11use ui::prelude::*;
12use workspace::Item;
13
14pub struct ProposedChangesEditor {
15 editor: View<Editor>,
16 _subscriptions: Vec<Subscription>,
17 _recalculate_diffs_task: Task<Option<()>>,
18 recalculate_diffs_tx: mpsc::UnboundedSender<Model<Buffer>>,
19}
20
21pub struct ProposedChangesBuffer<T> {
22 pub buffer: Model<Buffer>,
23 pub ranges: Vec<Range<T>>,
24}
25
26impl ProposedChangesEditor {
27 pub fn new<T: ToOffset>(
28 buffers: Vec<ProposedChangesBuffer<T>>,
29 project: Option<Model<Project>>,
30 cx: &mut ViewContext<Self>,
31 ) -> Self {
32 let mut subscriptions = Vec::new();
33 let multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite));
34
35 for buffer in buffers {
36 let branch_buffer = buffer.buffer.update(cx, |buffer, cx| buffer.branch(cx));
37 subscriptions.push(cx.subscribe(&branch_buffer, Self::on_buffer_event));
38
39 multibuffer.update(cx, |multibuffer, cx| {
40 multibuffer.push_excerpts(
41 branch_buffer,
42 buffer.ranges.into_iter().map(|range| ExcerptRange {
43 context: range,
44 primary: None,
45 }),
46 cx,
47 );
48 });
49 }
50
51 let (recalculate_diffs_tx, mut recalculate_diffs_rx) = mpsc::unbounded();
52
53 Self {
54 editor: cx
55 .new_view(|cx| Editor::for_multibuffer(multibuffer.clone(), project, true, cx)),
56 recalculate_diffs_tx,
57 _recalculate_diffs_task: cx.spawn(|_, mut cx| async move {
58 let mut buffers_to_diff = HashSet::default();
59 while let Some(buffer) = recalculate_diffs_rx.next().await {
60 buffers_to_diff.insert(buffer);
61
62 loop {
63 cx.background_executor()
64 .timer(Duration::from_millis(250))
65 .await;
66 let mut had_further_changes = false;
67 while let Ok(next_buffer) = recalculate_diffs_rx.try_next() {
68 buffers_to_diff.insert(next_buffer?);
69 had_further_changes = true;
70 }
71 if !had_further_changes {
72 break;
73 }
74 }
75
76 join_all(buffers_to_diff.drain().filter_map(|buffer| {
77 buffer
78 .update(&mut cx, |buffer, cx| buffer.recalculate_diff(cx))
79 .ok()?
80 }))
81 .await;
82 }
83 None
84 }),
85 _subscriptions: subscriptions,
86 }
87 }
88
89 fn on_buffer_event(
90 &mut self,
91 buffer: Model<Buffer>,
92 event: &BufferEvent,
93 _cx: &mut ViewContext<Self>,
94 ) {
95 if let BufferEvent::Edited = event {
96 self.recalculate_diffs_tx.unbounded_send(buffer).ok();
97 }
98 }
99}
100
101impl Render for ProposedChangesEditor {
102 fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
103 self.editor.clone()
104 }
105}
106
107impl FocusableView for ProposedChangesEditor {
108 fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
109 self.editor.focus_handle(cx)
110 }
111}
112
113impl EventEmitter<EditorEvent> for ProposedChangesEditor {}
114
115impl Item for ProposedChangesEditor {
116 type Event = EditorEvent;
117
118 fn tab_icon(&self, _cx: &ui::WindowContext) -> Option<Icon> {
119 Some(Icon::new(IconName::Pencil))
120 }
121
122 fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
123 Some("Proposed changes".into())
124 }
125}