1use crate::file_command::{FileCommandMetadata, FileSlashCommand};
  2use anyhow::{Result, anyhow};
  3use assistant_slash_command::{
  4    ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
  5    SlashCommandResult,
  6};
  7use collections::HashSet;
  8use futures::future;
  9use gpui::{App, Task, WeakEntity, Window};
 10use language::{BufferSnapshot, LspAdapterDelegate};
 11use std::sync::{Arc, atomic::AtomicBool};
 12use text::OffsetRangeExt;
 13use ui::prelude::*;
 14use workspace::Workspace;
 15
 16pub struct DeltaSlashCommand;
 17
 18impl SlashCommand for DeltaSlashCommand {
 19    fn name(&self) -> String {
 20        "delta".into()
 21    }
 22
 23    fn description(&self) -> String {
 24        "Re-insert changed files".into()
 25    }
 26
 27    fn menu_text(&self) -> String {
 28        self.description()
 29    }
 30
 31    fn icon(&self) -> IconName {
 32        IconName::Diff
 33    }
 34
 35    fn requires_argument(&self) -> bool {
 36        false
 37    }
 38
 39    fn complete_argument(
 40        self: Arc<Self>,
 41        _arguments: &[String],
 42        _cancellation_flag: Arc<AtomicBool>,
 43        _workspace: Option<WeakEntity<Workspace>>,
 44        _window: &mut Window,
 45        _cx: &mut App,
 46    ) -> Task<Result<Vec<ArgumentCompletion>>> {
 47        Task::ready(Err(anyhow!("this command does not require argument")))
 48    }
 49
 50    fn run(
 51        self: Arc<Self>,
 52        _arguments: &[String],
 53        context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
 54        context_buffer: BufferSnapshot,
 55        workspace: WeakEntity<Workspace>,
 56        delegate: Option<Arc<dyn LspAdapterDelegate>>,
 57        window: &mut Window,
 58        cx: &mut App,
 59    ) -> Task<SlashCommandResult> {
 60        let mut paths = HashSet::default();
 61        let mut file_command_old_outputs = Vec::new();
 62        let mut file_command_new_outputs = Vec::new();
 63
 64        for section in context_slash_command_output_sections.iter().rev() {
 65            if let Some(metadata) = section
 66                .metadata
 67                .as_ref()
 68                .and_then(|value| serde_json::from_value::<FileCommandMetadata>(value.clone()).ok())
 69                && paths.insert(metadata.path.clone())
 70            {
 71                file_command_old_outputs.push(
 72                    context_buffer
 73                        .as_rope()
 74                        .slice(section.range.to_offset(&context_buffer)),
 75                );
 76                file_command_new_outputs.push(Arc::new(FileSlashCommand).run(
 77                    std::slice::from_ref(&metadata.path),
 78                    context_slash_command_output_sections,
 79                    context_buffer.clone(),
 80                    workspace.clone(),
 81                    delegate.clone(),
 82                    window,
 83                    cx,
 84                ));
 85            }
 86        }
 87
 88        cx.background_spawn(async move {
 89            let mut output = SlashCommandOutput::default();
 90            let mut changes_detected = false;
 91
 92            let file_command_new_outputs = future::join_all(file_command_new_outputs).await;
 93            for (old_text, new_output) in file_command_old_outputs
 94                .into_iter()
 95                .zip(file_command_new_outputs)
 96            {
 97                if let Ok(new_output) = new_output
 98                    && let Ok(new_output) = SlashCommandOutput::from_event_stream(new_output).await
 99                    && let Some(file_command_range) = new_output.sections.first()
100                {
101                    let new_text = &new_output.text[file_command_range.range.clone()];
102                    if old_text.chars().ne(new_text.chars()) {
103                        changes_detected = true;
104                        output
105                            .sections
106                            .extend(new_output.sections.into_iter().map(|section| {
107                                SlashCommandOutputSection {
108                                    range: output.text.len() + section.range.start
109                                        ..output.text.len() + section.range.end,
110                                    icon: section.icon,
111                                    label: section.label,
112                                    metadata: section.metadata,
113                                }
114                            }));
115                        output.text.push_str(&new_output.text);
116                    }
117                }
118            }
119
120            anyhow::ensure!(changes_detected, "no new changes detected");
121            Ok(output.into_event_stream())
122        })
123    }
124}