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 menu_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 _workspace: Option<WeakView<Workspace>>,
43 _cx: &mut AppContext,
44 ) -> Task<Result<Vec<String>>> {
45 Task::ready(Ok(Vec::new()))
46 }
47
48 fn run(
49 self: Arc<Self>,
50 argument: Option<&str>,
51 _workspace: WeakView<Workspace>,
52 delegate: Arc<dyn LspAdapterDelegate>,
53 cx: &mut WindowContext,
54 ) -> Task<Result<SlashCommandOutput>> {
55 let command_name = SharedString::from(self.command.name.clone());
56 let argument = argument.map(|arg| arg.to_string());
57 let text = cx.background_executor().spawn(async move {
58 let output = self
59 .extension
60 .call({
61 let this = self.clone();
62 move |extension, store| {
63 async move {
64 let resource = store.data_mut().table().push(delegate)?;
65 let output = extension
66 .call_run_slash_command(
67 store,
68 &this.command,
69 argument.as_deref(),
70 resource,
71 )
72 .await?
73 .map_err(|e| anyhow!("{}", e))?;
74
75 anyhow::Ok(output)
76 }
77 .boxed()
78 }
79 })
80 .await?;
81 output.ok_or_else(|| anyhow!("no output from command: {}", self.command.name))
82 });
83 cx.foreground_executor().spawn(async move {
84 let text = text.await?;
85 let range = 0..text.len();
86 Ok(SlashCommandOutput {
87 text,
88 sections: vec![SlashCommandOutputSection {
89 range,
90 render_placeholder: Arc::new({
91 let command_name = command_name.clone();
92 move |id, unfold, _cx| {
93 ButtonLike::new(id)
94 .style(ButtonStyle::Filled)
95 .layer(ElevationIndex::ElevatedSurface)
96 .child(Icon::new(IconName::Code))
97 .child(Label::new(command_name.clone()))
98 .on_click(move |_event, cx| unfold(cx))
99 .into_any_element()
100 }
101 }),
102 }],
103 run_commands_in_text: false,
104 })
105 })
106 }
107}