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::stream::StreamExt;
15use ui::prelude::*;
16use workspace::Workspace;
17
18pub struct StreamingExampleSlashCommandFeatureFlag;
19
20impl FeatureFlag for StreamingExampleSlashCommandFeatureFlag {
21 const NAME: &'static str = "streaming-example-slash-command";
22}
23
24pub struct StreamingExampleSlashCommand;
25
26impl SlashCommand for StreamingExampleSlashCommand {
27 fn name(&self) -> String {
28 "streaming-example".into()
29 }
30
31 fn description(&self) -> String {
32 "An example slash command that showcases streaming.".into()
33 }
34
35 fn menu_text(&self) -> String {
36 self.description()
37 }
38
39 fn requires_argument(&self) -> bool {
40 false
41 }
42
43 fn complete_argument(
44 self: Arc<Self>,
45 _arguments: &[String],
46 _cancel: Arc<AtomicBool>,
47 _workspace: Option<WeakEntity<Workspace>>,
48 _window: &mut Window,
49 _cx: &mut App,
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: WeakEntity<Workspace>,
60 _delegate: Option<Arc<dyn LspAdapterDelegate>>,
61 _: &mut Window,
62 cx: &mut App,
63 ) -> Task<SlashCommandResult> {
64 let (events_tx, events_rx) = mpsc::unbounded();
65 let executor = cx.background_executor().clone();
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 executor.timer(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 executor.timer(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}