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}