proposed_changes_editor.rs

  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}