1use std::sync::Arc;
2use std::sync::atomic::AtomicBool;
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, WeakEntity};
13use language::{BufferSnapshot, LspAdapterDelegate};
14use smol::Timer;
15use smol::stream::StreamExt;
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 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<WeakEntity<Workspace>>,
49 _window: &mut Window,
50 _cx: &mut App,
51 ) -> Task<Result<Vec<ArgumentCompletion>>> {
52 Task::ready(Ok(Vec::new()))
53 }
54
55 fn run(
56 self: Arc<Self>,
57 _arguments: &[String],
58 _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
59 _context_buffer: BufferSnapshot,
60 _workspace: WeakEntity<Workspace>,
61 _delegate: Option<Arc<dyn LspAdapterDelegate>>,
62 _: &mut Window,
63 cx: &mut App,
64 ) -> Task<SlashCommandResult> {
65 let (events_tx, events_rx) = mpsc::unbounded();
66 cx.background_spawn(async move {
67 events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
68 icon: IconName::FileRust,
69 label: "Section 1".into(),
70 metadata: None,
71 }))?;
72 events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
73 SlashCommandContent::Text {
74 text: "Hello".into(),
75 run_commands_in_text: false,
76 },
77 )))?;
78 events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
79
80 Timer::after(Duration::from_secs(1)).await;
81
82 events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
83 icon: IconName::FileRust,
84 label: "Section 2".into(),
85 metadata: None,
86 }))?;
87 events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
88 SlashCommandContent::Text {
89 text: "World".into(),
90 run_commands_in_text: false,
91 },
92 )))?;
93 events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
94
95 for n in 1..=10 {
96 Timer::after(Duration::from_secs(1)).await;
97
98 events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
99 icon: IconName::StarFilled,
100 label: format!("Section {n}").into(),
101 metadata: None,
102 }))?;
103 events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
104 SlashCommandContent::Text {
105 text: "lorem ipsum ".repeat(n).trim().into(),
106 run_commands_in_text: false,
107 },
108 )))?;
109 events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
110 }
111
112 anyhow::Ok(())
113 })
114 .detach_and_log_err(cx);
115
116 Task::ready(Ok(events_rx.boxed()))
117 }
118}