proposed_changes_editor.rs

  1use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SemanticsProvider};
  2use collections::HashSet;
  3use futures::{channel::mpsc, future::join_all};
  4use gpui::{App, Entity, EventEmitter, Focusable, Render, Subscription, Task};
  5use language::{Buffer, BufferEvent, Capability};
  6use multi_buffer::{ExcerptRange, MultiBuffer};
  7use project::{buffer_store::BufferChangeSet, Project};
  8use smol::stream::StreamExt;
  9use std::{any::TypeId, ops::Range, rc::Rc, time::Duration};
 10use text::ToOffset;
 11use ui::{prelude::*, ButtonLike, KeyBinding};
 12use workspace::{
 13    searchable::SearchableItemHandle, Item, ItemHandle as _, ToolbarItemEvent, ToolbarItemLocation,
 14    ToolbarItemView, Workspace,
 15};
 16
 17pub struct ProposedChangesEditor {
 18    editor: Entity<Editor>,
 19    multibuffer: Entity<MultiBuffer>,
 20    title: SharedString,
 21    buffer_entries: Vec<BufferEntry>,
 22    _recalculate_diffs_task: Task<Option<()>>,
 23    recalculate_diffs_tx: mpsc::UnboundedSender<RecalculateDiff>,
 24}
 25
 26pub struct ProposedChangeLocation<T> {
 27    pub buffer: Entity<Buffer>,
 28    pub ranges: Vec<Range<T>>,
 29}
 30
 31struct BufferEntry {
 32    base: Entity<Buffer>,
 33    branch: Entity<Buffer>,
 34    _subscription: Subscription,
 35}
 36
 37pub struct ProposedChangesEditorToolbar {
 38    current_editor: Option<Entity<ProposedChangesEditor>>,
 39}
 40
 41struct RecalculateDiff {
 42    buffer: Entity<Buffer>,
 43    debounce: bool,
 44}
 45
 46/// A provider of code semantics for branch buffers.
 47///
 48/// Requests in edited regions will return nothing, but requests in unchanged
 49/// regions will be translated into the base buffer's coordinates.
 50struct BranchBufferSemanticsProvider(Rc<dyn SemanticsProvider>);
 51
 52impl ProposedChangesEditor {
 53    pub fn new<T: ToOffset>(
 54        title: impl Into<SharedString>,
 55        locations: Vec<ProposedChangeLocation<T>>,
 56        project: Option<Entity<Project>>,
 57        window: &mut Window,
 58        cx: &mut Context<Self>,
 59    ) -> Self {
 60        let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 61        let (recalculate_diffs_tx, mut recalculate_diffs_rx) = mpsc::unbounded();
 62        let mut this = Self {
 63            editor: cx.new(|cx| {
 64                let mut editor =
 65                    Editor::for_multibuffer(multibuffer.clone(), project, true, window, cx);
 66                editor.set_expand_all_diff_hunks(cx);
 67                editor.set_completion_provider(None);
 68                editor.clear_code_action_providers();
 69                editor.set_semantics_provider(
 70                    editor
 71                        .semantics_provider()
 72                        .map(|provider| Rc::new(BranchBufferSemanticsProvider(provider)) as _),
 73                );
 74                editor
 75            }),
 76            multibuffer,
 77            title: title.into(),
 78            buffer_entries: Vec::new(),
 79            recalculate_diffs_tx,
 80            _recalculate_diffs_task: cx.spawn_in(window, |this, mut cx| async move {
 81                let mut buffers_to_diff = HashSet::default();
 82                while let Some(mut recalculate_diff) = recalculate_diffs_rx.next().await {
 83                    buffers_to_diff.insert(recalculate_diff.buffer);
 84
 85                    while recalculate_diff.debounce {
 86                        cx.background_executor()
 87                            .timer(Duration::from_millis(50))
 88                            .await;
 89                        let mut had_further_changes = false;
 90                        while let Ok(next_recalculate_diff) = recalculate_diffs_rx.try_next() {
 91                            let next_recalculate_diff = next_recalculate_diff?;
 92                            recalculate_diff.debounce &= next_recalculate_diff.debounce;
 93                            buffers_to_diff.insert(next_recalculate_diff.buffer);
 94                            had_further_changes = true;
 95                        }
 96                        if !had_further_changes {
 97                            break;
 98                        }
 99                    }
100
101                    let recalculate_diff_futures = this
102                        .update(&mut cx, |this, cx| {
103                            buffers_to_diff
104                                .drain()
105                                .filter_map(|buffer| {
106                                    let buffer = buffer.read(cx);
107                                    let base_buffer = buffer.base_buffer()?;
108                                    let buffer = buffer.text_snapshot();
109                                    let change_set = this
110                                        .multibuffer
111                                        .read(cx)
112                                        .change_set_for(buffer.remote_id())?;
113                                    Some(change_set.update(cx, |change_set, cx| {
114                                        change_set.set_base_text(
115                                            base_buffer.read(cx).text(),
116                                            buffer,
117                                            cx,
118                                        )
119                                    }))
120                                })
121                                .collect::<Vec<_>>()
122                        })
123                        .ok()?;
124
125                    join_all(recalculate_diff_futures).await;
126                }
127                None
128            }),
129        };
130        this.reset_locations(locations, window, cx);
131        this
132    }
133
134    pub fn branch_buffer_for_base(&self, base_buffer: &Entity<Buffer>) -> Option<Entity<Buffer>> {
135        self.buffer_entries.iter().find_map(|entry| {
136            if &entry.base == base_buffer {
137                Some(entry.branch.clone())
138            } else {
139                None
140            }
141        })
142    }
143
144    pub fn set_title(&mut self, title: SharedString, cx: &mut Context<Self>) {
145        self.title = title;
146        cx.notify();
147    }
148
149    pub fn reset_locations<T: ToOffset>(
150        &mut self,
151        locations: Vec<ProposedChangeLocation<T>>,
152        window: &mut Window,
153        cx: &mut Context<Self>,
154    ) {
155        // Undo all branch changes
156        for entry in &self.buffer_entries {
157            let base_version = entry.base.read(cx).version();
158            entry.branch.update(cx, |buffer, cx| {
159                let undo_counts = buffer
160                    .operations()
161                    .iter()
162                    .filter_map(|(timestamp, _)| {
163                        if !base_version.observed(*timestamp) {
164                            Some((*timestamp, u32::MAX))
165                        } else {
166                            None
167                        }
168                    })
169                    .collect();
170                buffer.undo_operations(undo_counts, cx);
171            });
172        }
173
174        self.multibuffer.update(cx, |multibuffer, cx| {
175            multibuffer.clear(cx);
176        });
177
178        let mut buffer_entries = Vec::new();
179        let mut new_change_sets = Vec::new();
180        for location in locations {
181            let branch_buffer;
182            if let Some(ix) = self
183                .buffer_entries
184                .iter()
185                .position(|entry| entry.base == location.buffer)
186            {
187                let entry = self.buffer_entries.remove(ix);
188                branch_buffer = entry.branch.clone();
189                buffer_entries.push(entry);
190            } else {
191                branch_buffer = location.buffer.update(cx, |buffer, cx| buffer.branch(cx));
192                new_change_sets.push(cx.new(|cx| {
193                    let mut change_set = BufferChangeSet::new(&branch_buffer, cx);
194                    let _ = change_set.set_base_text(
195                        location.buffer.read(cx).text(),
196                        branch_buffer.read(cx).text_snapshot(),
197                        cx,
198                    );
199                    change_set
200                }));
201                buffer_entries.push(BufferEntry {
202                    branch: branch_buffer.clone(),
203                    base: location.buffer.clone(),
204                    _subscription: cx.subscribe(&branch_buffer, Self::on_buffer_event),
205                });
206            }
207
208            self.multibuffer.update(cx, |multibuffer, cx| {
209                multibuffer.push_excerpts(
210                    branch_buffer,
211                    location.ranges.into_iter().map(|range| ExcerptRange {
212                        context: range,
213                        primary: None,
214                    }),
215                    cx,
216                );
217            });
218        }
219
220        self.buffer_entries = buffer_entries;
221        self.editor.update(cx, |editor, cx| {
222            editor.change_selections(None, window, cx, |selections| selections.refresh());
223            editor.buffer.update(cx, |buffer, cx| {
224                for change_set in new_change_sets {
225                    buffer.add_change_set(change_set, cx)
226                }
227            })
228        });
229    }
230
231    pub fn recalculate_all_buffer_diffs(&self) {
232        for (ix, entry) in self.buffer_entries.iter().enumerate().rev() {
233            self.recalculate_diffs_tx
234                .unbounded_send(RecalculateDiff {
235                    buffer: entry.branch.clone(),
236                    debounce: ix > 0,
237                })
238                .ok();
239        }
240    }
241
242    fn on_buffer_event(
243        &mut self,
244        buffer: Entity<Buffer>,
245        event: &BufferEvent,
246        _cx: &mut Context<Self>,
247    ) {
248        match event {
249            BufferEvent::Operation { .. } => {
250                self.recalculate_diffs_tx
251                    .unbounded_send(RecalculateDiff {
252                        buffer,
253                        debounce: true,
254                    })
255                    .ok();
256            }
257            // BufferEvent::DiffBaseChanged => {
258            //     self.recalculate_diffs_tx
259            //         .unbounded_send(RecalculateDiff {
260            //             buffer,
261            //             debounce: false,
262            //         })
263            //         .ok();
264            // }
265            _ => (),
266        }
267    }
268}
269
270impl Render for ProposedChangesEditor {
271    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
272        div()
273            .size_full()
274            .key_context("ProposedChangesEditor")
275            .child(self.editor.clone())
276    }
277}
278
279impl Focusable for ProposedChangesEditor {
280    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
281        self.editor.focus_handle(cx)
282    }
283}
284
285impl EventEmitter<EditorEvent> for ProposedChangesEditor {}
286
287impl Item for ProposedChangesEditor {
288    type Event = EditorEvent;
289
290    fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
291        Some(Icon::new(IconName::Diff))
292    }
293
294    fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
295        Some(self.title.clone())
296    }
297
298    fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
299        Some(Box::new(self.editor.clone()))
300    }
301
302    fn act_as_type<'a>(
303        &'a self,
304        type_id: TypeId,
305        self_handle: &'a Entity<Self>,
306        _: &'a App,
307    ) -> Option<gpui::AnyView> {
308        if type_id == TypeId::of::<Self>() {
309            Some(self_handle.to_any())
310        } else if type_id == TypeId::of::<Editor>() {
311            Some(self.editor.to_any())
312        } else {
313            None
314        }
315    }
316
317    fn added_to_workspace(
318        &mut self,
319        workspace: &mut Workspace,
320        window: &mut Window,
321        cx: &mut Context<Self>,
322    ) {
323        self.editor.update(cx, |editor, cx| {
324            Item::added_to_workspace(editor, workspace, window, cx)
325        });
326    }
327
328    fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
329        self.editor
330            .update(cx, |editor, cx| editor.deactivated(window, cx));
331    }
332
333    fn navigate(
334        &mut self,
335        data: Box<dyn std::any::Any>,
336        window: &mut Window,
337        cx: &mut Context<Self>,
338    ) -> bool {
339        self.editor
340            .update(cx, |editor, cx| Item::navigate(editor, data, window, cx))
341    }
342
343    fn set_nav_history(
344        &mut self,
345        nav_history: workspace::ItemNavHistory,
346        window: &mut Window,
347        cx: &mut Context<Self>,
348    ) {
349        self.editor.update(cx, |editor, cx| {
350            Item::set_nav_history(editor, nav_history, window, cx)
351        });
352    }
353
354    fn can_save(&self, cx: &App) -> bool {
355        self.editor.read(cx).can_save(cx)
356    }
357
358    fn save(
359        &mut self,
360        format: bool,
361        project: Entity<Project>,
362        window: &mut Window,
363        cx: &mut Context<Self>,
364    ) -> Task<gpui::Result<()>> {
365        self.editor.update(cx, |editor, cx| {
366            Item::save(editor, format, project, window, cx)
367        })
368    }
369}
370
371impl ProposedChangesEditorToolbar {
372    pub fn new() -> Self {
373        Self {
374            current_editor: None,
375        }
376    }
377
378    fn get_toolbar_item_location(&self) -> ToolbarItemLocation {
379        if self.current_editor.is_some() {
380            ToolbarItemLocation::PrimaryRight
381        } else {
382            ToolbarItemLocation::Hidden
383        }
384    }
385}
386
387impl Render for ProposedChangesEditorToolbar {
388    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
389        let button_like = ButtonLike::new("apply-changes").child(Label::new("Apply All"));
390
391        match &self.current_editor {
392            Some(editor) => {
393                let focus_handle = editor.focus_handle(cx);
394                let keybinding =
395                    KeyBinding::for_action_in(&ApplyAllDiffHunks, &focus_handle, window)
396                        .map(|binding| binding.into_any_element());
397
398                button_like.children(keybinding).on_click({
399                    move |_event, window, cx| {
400                        focus_handle.dispatch_action(&ApplyAllDiffHunks, window, cx)
401                    }
402                })
403            }
404            None => button_like.disabled(true),
405        }
406    }
407}
408
409impl EventEmitter<ToolbarItemEvent> for ProposedChangesEditorToolbar {}
410
411impl ToolbarItemView for ProposedChangesEditorToolbar {
412    fn set_active_pane_item(
413        &mut self,
414        active_pane_item: Option<&dyn workspace::ItemHandle>,
415        _window: &mut Window,
416        _cx: &mut Context<Self>,
417    ) -> workspace::ToolbarItemLocation {
418        self.current_editor =
419            active_pane_item.and_then(|item| item.downcast::<ProposedChangesEditor>());
420        self.get_toolbar_item_location()
421    }
422}
423
424impl BranchBufferSemanticsProvider {
425    fn to_base(
426        &self,
427        buffer: &Entity<Buffer>,
428        positions: &[text::Anchor],
429        cx: &App,
430    ) -> Option<Entity<Buffer>> {
431        let base_buffer = buffer.read(cx).base_buffer()?;
432        let version = base_buffer.read(cx).version();
433        if positions
434            .iter()
435            .any(|position| !version.observed(position.timestamp))
436        {
437            return None;
438        }
439        Some(base_buffer)
440    }
441}
442
443impl SemanticsProvider for BranchBufferSemanticsProvider {
444    fn hover(
445        &self,
446        buffer: &Entity<Buffer>,
447        position: text::Anchor,
448        cx: &mut App,
449    ) -> Option<Task<Vec<project::Hover>>> {
450        let buffer = self.to_base(buffer, &[position], cx)?;
451        self.0.hover(&buffer, position, cx)
452    }
453
454    fn inlay_hints(
455        &self,
456        buffer: Entity<Buffer>,
457        range: Range<text::Anchor>,
458        cx: &mut App,
459    ) -> Option<Task<anyhow::Result<Vec<project::InlayHint>>>> {
460        let buffer = self.to_base(&buffer, &[range.start, range.end], cx)?;
461        self.0.inlay_hints(buffer, range, cx)
462    }
463
464    fn resolve_inlay_hint(
465        &self,
466        hint: project::InlayHint,
467        buffer: Entity<Buffer>,
468        server_id: lsp::LanguageServerId,
469        cx: &mut App,
470    ) -> Option<Task<anyhow::Result<project::InlayHint>>> {
471        let buffer = self.to_base(&buffer, &[], cx)?;
472        self.0.resolve_inlay_hint(hint, buffer, server_id, cx)
473    }
474
475    fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &App) -> bool {
476        if let Some(buffer) = self.to_base(&buffer, &[], cx) {
477            self.0.supports_inlay_hints(&buffer, cx)
478        } else {
479            false
480        }
481    }
482
483    fn document_highlights(
484        &self,
485        buffer: &Entity<Buffer>,
486        position: text::Anchor,
487        cx: &mut App,
488    ) -> Option<Task<gpui::Result<Vec<project::DocumentHighlight>>>> {
489        let buffer = self.to_base(&buffer, &[position], cx)?;
490        self.0.document_highlights(&buffer, position, cx)
491    }
492
493    fn definitions(
494        &self,
495        buffer: &Entity<Buffer>,
496        position: text::Anchor,
497        kind: crate::GotoDefinitionKind,
498        cx: &mut App,
499    ) -> Option<Task<gpui::Result<Vec<project::LocationLink>>>> {
500        let buffer = self.to_base(&buffer, &[position], cx)?;
501        self.0.definitions(&buffer, position, kind, cx)
502    }
503
504    fn range_for_rename(
505        &self,
506        _: &Entity<Buffer>,
507        _: text::Anchor,
508        _: &mut App,
509    ) -> Option<Task<gpui::Result<Option<Range<text::Anchor>>>>> {
510        None
511    }
512
513    fn perform_rename(
514        &self,
515        _: &Entity<Buffer>,
516        _: text::Anchor,
517        _: String,
518        _: &mut App,
519    ) -> Option<Task<gpui::Result<project::ProjectTransaction>>> {
520        None
521    }
522}