1use std::sync::atomic::AtomicBool;
2use std::sync::Arc;
3use std::time::Duration;
4
5use anyhow::Result;
6use assistant_slash_command::{
7 ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent,
8 SlashCommandOutputSection, SlashCommandResult,
9};
10use feature_flags::FeatureFlag;
11use futures::channel::mpsc;
12use gpui::{Task, WeakView};
13use language::{BufferSnapshot, LspAdapterDelegate};
14use smol::stream::StreamExt;
15use smol::Timer;
16use ui::prelude::*;
17use workspace::Workspace;
18
19pub struct StreamingExampleSlashCommandFeatureFlag;
20
21impl FeatureFlag for StreamingExampleSlashCommandFeatureFlag {
22 const NAME: &'static str = "streaming-example-slash-command";
23}
24
25pub(crate) struct StreamingExampleSlashCommand;
26
27impl SlashCommand for StreamingExampleSlashCommand {
28 fn name(&self) -> String {
29 "streaming-example".into()
30 }
31
32 fn description(&self) -> String {
33 "An example slash command that showcases streaming.".into()
34 }
35
36 fn menu_text(&self) -> String {
37 self.description()
38 }
39
40 fn requires_argument(&self) -> bool {
41 false
42 }
43
44 fn complete_argument(
45 self: Arc<Self>,
46 _arguments: &[String],
47 _cancel: Arc<AtomicBool>,
48 _workspace: Option<WeakView<Workspace>>,
49 _cx: &mut WindowContext,
50 ) -> Task<Result<Vec<ArgumentCompletion>>> {
51 Task::ready(Ok(Vec::new()))
52 }
53
54 fn run(
55 self: Arc<Self>,
56 _arguments: &[String],
57 _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
58 _context_buffer: BufferSnapshot,
59 _workspace: WeakView<Workspace>,
60 _delegate: Option<Arc<dyn LspAdapterDelegate>>,
61 cx: &mut WindowContext,
62 ) -> Task<SlashCommandResult> {
63 let (events_tx, events_rx) = mpsc::unbounded();
64 cx.background_executor()
65 .spawn(async move {
66 events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
67 icon: IconName::FileRust,
68 label: "Section 1".into(),
69 metadata: None,
70 }))?;
71 events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
72 SlashCommandContent::Text {
73 text: "Hello".into(),
74 run_commands_in_text: false,
75 },
76 )))?;
77 events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
78
79 Timer::after(Duration::from_secs(1)).await;
80
81 events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
82 icon: IconName::FileRust,
83 label: "Section 2".into(),
84 metadata: None,
85 }))?;
86 events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
87 SlashCommandContent::Text {
88 text: "World".into(),
89 run_commands_in_text: false,
90 },
91 )))?;
92 events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
93
94 for n in 1..=10 {
95 Timer::after(Duration::from_secs(1)).await;
96
97 events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
98 icon: IconName::StarFilled,
99 label: format!("Section {n}").into(),
100 metadata: None,
101 }))?;
102 events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
103 SlashCommandContent::Text {
104 text: "lorem ipsum ".repeat(n).trim().into(),
105 run_commands_in_text: false,
106 },
107 )))?;
108 events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
109 }
110
111 anyhow::Ok(())
112 })
113 .detach_and_log_err(cx);
114
115 Task::ready(Ok(events_rx.boxed()))
116 }
117}