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, WeakEntity};
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 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_executor()
67 .spawn(async move {
68 events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
69 icon: IconName::FileRust,
70 label: "Section 1".into(),
71 metadata: None,
72 }))?;
73 events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
74 SlashCommandContent::Text {
75 text: "Hello".into(),
76 run_commands_in_text: false,
77 },
78 )))?;
79 events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
80
81 Timer::after(Duration::from_secs(1)).await;
82
83 events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
84 icon: IconName::FileRust,
85 label: "Section 2".into(),
86 metadata: None,
87 }))?;
88 events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
89 SlashCommandContent::Text {
90 text: "World".into(),
91 run_commands_in_text: false,
92 },
93 )))?;
94 events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
95
96 for n in 1..=10 {
97 Timer::after(Duration::from_secs(1)).await;
98
99 events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
100 icon: IconName::StarFilled,
101 label: format!("Section {n}").into(),
102 metadata: None,
103 }))?;
104 events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
105 SlashCommandContent::Text {
106 text: "lorem ipsum ".repeat(n).trim().into(),
107 run_commands_in_text: false,
108 },
109 )))?;
110 events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
111 }
112
113 anyhow::Ok(())
114 })
115 .detach_and_log_err(cx);
116
117 Task::ready(Ok(events_rx.boxed()))
118 }
119}