delta_command.rs

  1use crate::slash_command::file_command::{FileCommandMetadata, FileSlashCommand};
  2use anyhow::{anyhow, Result};
  3use assistant_slash_command::{
  4    ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
  5    SlashCommandResult,
  6};
  7use collections::HashSet;
  8use futures::future;
  9use gpui::{Task, WeakView, WindowContext};
 10use language::{BufferSnapshot, LspAdapterDelegate};
 11use std::sync::{atomic::AtomicBool, Arc};
 12use text::OffsetRangeExt;
 13use ui::prelude::*;
 14use workspace::Workspace;
 15
 16pub(crate) 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<WeakView<Workspace>>,
 44        _cx: &mut WindowContext,
 45    ) -> Task<Result<Vec<ArgumentCompletion>>> {
 46        Task::ready(Err(anyhow!("this command does not require argument")))
 47    }
 48
 49    fn run(
 50        self: Arc<Self>,
 51        _arguments: &[String],
 52        context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
 53        context_buffer: BufferSnapshot,
 54        workspace: WeakView<Workspace>,
 55        delegate: Option<Arc<dyn LspAdapterDelegate>>,
 56        cx: &mut WindowContext,
 57    ) -> Task<SlashCommandResult> {
 58        let mut paths = HashSet::default();
 59        let mut file_command_old_outputs = Vec::new();
 60        let mut file_command_new_outputs = Vec::new();
 61
 62        for section in context_slash_command_output_sections.iter().rev() {
 63            if let Some(metadata) = section
 64                .metadata
 65                .as_ref()
 66                .and_then(|value| serde_json::from_value::<FileCommandMetadata>(value.clone()).ok())
 67            {
 68                if paths.insert(metadata.path.clone()) {
 69                    file_command_old_outputs.push(
 70                        context_buffer
 71                            .as_rope()
 72                            .slice(section.range.to_offset(&context_buffer)),
 73                    );
 74                    file_command_new_outputs.push(Arc::new(FileSlashCommand).run(
 75                        &[metadata.path.clone()],
 76                        context_slash_command_output_sections,
 77                        context_buffer.clone(),
 78                        workspace.clone(),
 79                        delegate.clone(),
 80                        cx,
 81                    ));
 82                }
 83            }
 84        }
 85
 86        cx.background_executor().spawn(async move {
 87            let mut output = SlashCommandOutput::default();
 88            let mut changes_detected = false;
 89
 90            let file_command_new_outputs = future::join_all(file_command_new_outputs).await;
 91            for (old_text, new_output) in file_command_old_outputs
 92                .into_iter()
 93                .zip(file_command_new_outputs)
 94            {
 95                if let Ok(new_output) = new_output {
 96                    if let Ok(new_output) = SlashCommandOutput::from_event_stream(new_output).await
 97                    {
 98                        if let Some(file_command_range) = new_output.sections.first() {
 99                            let new_text = &new_output.text[file_command_range.range.clone()];
100                            if old_text.chars().ne(new_text.chars()) {
101                                changes_detected = true;
102                                output.sections.extend(new_output.sections.into_iter().map(
103                                    |section| SlashCommandOutputSection {
104                                        range: output.text.len() + section.range.start
105                                            ..output.text.len() + section.range.end,
106                                        icon: section.icon,
107                                        label: section.label,
108                                        metadata: section.metadata,
109                                    },
110                                ));
111                                output.text.push_str(&new_output.text);
112                            }
113                        }
114                    }
115                }
116            }
117
118            if !changes_detected {
119                return Err(anyhow!("no new changes detected"));
120            }
121
122            Ok(output.to_event_stream())
123        })
124    }
125}