1use std::sync::{atomic::AtomicBool, Arc};
2
3use anyhow::{anyhow, Result};
4use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
5use futures::FutureExt;
6use gpui::{AppContext, IntoElement, Task, WeakView, WindowContext};
7use language::LspAdapterDelegate;
8use ui::{prelude::*, ButtonLike, ElevationIndex};
9use wasmtime_wasi::WasiView;
10use workspace::Workspace;
11
12use crate::wasm_host::{WasmExtension, WasmHost};
13
14pub struct ExtensionSlashCommand {
15 pub(crate) extension: WasmExtension,
16 #[allow(unused)]
17 pub(crate) host: Arc<WasmHost>,
18 pub(crate) command: crate::wit::SlashCommand,
19}
20
21impl SlashCommand for ExtensionSlashCommand {
22 fn name(&self) -> String {
23 self.command.name.clone()
24 }
25
26 fn description(&self) -> String {
27 self.command.description.clone()
28 }
29
30 fn tooltip_text(&self) -> String {
31 self.command.tooltip_text.clone()
32 }
33
34 fn requires_argument(&self) -> bool {
35 self.command.requires_argument
36 }
37
38 fn complete_argument(
39 &self,
40 _query: String,
41 _cancel: Arc<AtomicBool>,
42 _cx: &mut AppContext,
43 ) -> Task<Result<Vec<String>>> {
44 Task::ready(Ok(Vec::new()))
45 }
46
47 fn run(
48 self: Arc<Self>,
49 argument: Option<&str>,
50 _workspace: WeakView<Workspace>,
51 delegate: Arc<dyn LspAdapterDelegate>,
52 cx: &mut WindowContext,
53 ) -> Task<Result<SlashCommandOutput>> {
54 let command_name = SharedString::from(self.command.name.clone());
55 let argument = argument.map(|arg| arg.to_string());
56 let text = cx.background_executor().spawn(async move {
57 let output = self
58 .extension
59 .call({
60 let this = self.clone();
61 move |extension, store| {
62 async move {
63 let resource = store.data_mut().table().push(delegate)?;
64 let output = extension
65 .call_run_slash_command(
66 store,
67 &this.command,
68 argument.as_deref(),
69 resource,
70 )
71 .await?
72 .map_err(|e| anyhow!("{}", e))?;
73
74 anyhow::Ok(output)
75 }
76 .boxed()
77 }
78 })
79 .await?;
80 output.ok_or_else(|| anyhow!("no output from command: {}", self.command.name))
81 });
82 cx.foreground_executor().spawn(async move {
83 let text = text.await?;
84 let range = 0..text.len();
85 Ok(SlashCommandOutput {
86 text,
87 sections: vec![SlashCommandOutputSection {
88 range,
89 render_placeholder: Arc::new({
90 let command_name = command_name.clone();
91 move |id, unfold, _cx| {
92 ButtonLike::new(id)
93 .style(ButtonStyle::Filled)
94 .layer(ElevationIndex::ElevatedSurface)
95 .child(Icon::new(IconName::Code))
96 .child(Label::new(command_name.clone()))
97 .on_click(move |_event, cx| unfold(cx))
98 .into_any_element()
99 }
100 }),
101 }],
102 })
103 })
104 }
105}