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 { metadata: None }))?;
78 events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
79 SlashCommandContent::Text {
80 text: "\n".into(),
81 run_commands_in_text: false,
82 },
83 )))?;
84
85 Timer::after(Duration::from_secs(1)).await;
86
87 events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
88 icon: IconName::FileRust,
89 label: "Section 2".into(),
90 metadata: None,
91 }))?;
92 events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
93 SlashCommandContent::Text {
94 text: "World".into(),
95 run_commands_in_text: false,
96 },
97 )))?;
98 events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
99 events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
100 SlashCommandContent::Text {
101 text: "\n".into(),
102 run_commands_in_text: false,
103 },
104 )))?;
105
106 for n in 1..=10 {
107 Timer::after(Duration::from_secs(1)).await;
108
109 events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
110 icon: IconName::StarFilled,
111 label: format!("Section {n}").into(),
112 metadata: None,
113 }))?;
114 events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
115 SlashCommandContent::Text {
116 text: "lorem ipsum ".repeat(n).trim().into(),
117 run_commands_in_text: false,
118 },
119 )))?;
120 events_tx
121 .unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
122 events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
123 SlashCommandContent::Text {
124 text: "\n".into(),
125 run_commands_in_text: false,
126 },
127 )))?;
128 }
129
130 anyhow::Ok(())
131 })
132 .detach_and_log_err(cx);
133
134 Task::ready(Ok(events_rx.boxed()))
135 }
136}