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}