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 workspace::Workspace;
 14
 15pub(crate) struct DeltaSlashCommand;
 16
 17impl SlashCommand for DeltaSlashCommand {
 18    fn name(&self) -> String {
 19        "delta".into()
 20    }
 21
 22    fn description(&self) -> String {
 23        "Re-insert changed files".into()
 24    }
 25
 26    fn menu_text(&self) -> String {
 27        self.description()
 28    }
 29
 30    fn requires_argument(&self) -> bool {
 31        false
 32    }
 33
 34    fn complete_argument(
 35        self: Arc<Self>,
 36        _arguments: &[String],
 37        _cancellation_flag: Arc<AtomicBool>,
 38        _workspace: Option<WeakView<Workspace>>,
 39        _cx: &mut WindowContext,
 40    ) -> Task<Result<Vec<ArgumentCompletion>>> {
 41        Task::ready(Err(anyhow!("this command does not require argument")))
 42    }
 43
 44    fn run(
 45        self: Arc<Self>,
 46        _arguments: &[String],
 47        context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
 48        context_buffer: BufferSnapshot,
 49        workspace: WeakView<Workspace>,
 50        delegate: Option<Arc<dyn LspAdapterDelegate>>,
 51        cx: &mut WindowContext,
 52    ) -> Task<SlashCommandResult> {
 53        let mut paths = HashSet::default();
 54        let mut file_command_old_outputs = Vec::new();
 55        let mut file_command_new_outputs = Vec::new();
 56        for section in context_slash_command_output_sections.iter().rev() {
 57            if let Some(metadata) = section
 58                .metadata
 59                .as_ref()
 60                .and_then(|value| serde_json::from_value::<FileCommandMetadata>(value.clone()).ok())
 61            {
 62                if paths.insert(metadata.path.clone()) {
 63                    file_command_old_outputs.push(
 64                        context_buffer
 65                            .as_rope()
 66                            .slice(section.range.to_offset(&context_buffer)),
 67                    );
 68                    file_command_new_outputs.push(Arc::new(FileSlashCommand).run(
 69                        &[metadata.path.clone()],
 70                        context_slash_command_output_sections,
 71                        context_buffer.clone(),
 72                        workspace.clone(),
 73                        delegate.clone(),
 74                        cx,
 75                    ));
 76                }
 77            }
 78        }
 79
 80        cx.background_executor().spawn(async move {
 81            let mut output = SlashCommandOutput::default();
 82
 83            let file_command_new_outputs = future::join_all(file_command_new_outputs).await;
 84            for (old_text, new_output) in file_command_old_outputs
 85                .into_iter()
 86                .zip(file_command_new_outputs)
 87            {
 88                if let Ok(new_output) = new_output {
 89                    if let Ok(new_output) = SlashCommandOutput::from_event_stream(new_output).await
 90                    {
 91                        if let Some(file_command_range) = new_output.sections.first() {
 92                            let new_text = &new_output.text[file_command_range.range.clone()];
 93                            if old_text.chars().ne(new_text.chars()) {
 94                                output.sections.extend(new_output.sections.into_iter().map(
 95                                    |section| SlashCommandOutputSection {
 96                                        range: output.text.len() + section.range.start
 97                                            ..output.text.len() + section.range.end,
 98                                        icon: section.icon,
 99                                        label: section.label,
100                                        metadata: section.metadata,
101                                    },
102                                ));
103                                output.text.push_str(&new_output.text);
104                            }
105                        }
106                    }
107                }
108            }
109
110            Ok(output.to_event_stream())
111        })
112    }
113}