prompt_command.rs

  1use super::{SlashCommand, SlashCommandOutput};
  2use crate::prompt_library::PromptStore;
  3use anyhow::{anyhow, Context, Result};
  4use assistant_slash_command::SlashCommandOutputSection;
  5use gpui::{AppContext, Task, WeakView};
  6use language::LspAdapterDelegate;
  7use std::sync::{atomic::AtomicBool, Arc};
  8use ui::{prelude::*, ButtonLike, ElevationIndex};
  9use workspace::Workspace;
 10
 11pub(crate) struct PromptSlashCommand {
 12    store: Arc<PromptStore>,
 13}
 14
 15impl PromptSlashCommand {
 16    pub fn new(store: Arc<PromptStore>) -> Self {
 17        Self { store }
 18    }
 19}
 20
 21impl SlashCommand for PromptSlashCommand {
 22    fn name(&self) -> String {
 23        "prompt".into()
 24    }
 25
 26    fn description(&self) -> String {
 27        "insert prompt from library".into()
 28    }
 29
 30    fn menu_text(&self) -> String {
 31        "Insert Prompt from Library".into()
 32    }
 33
 34    fn requires_argument(&self) -> bool {
 35        true
 36    }
 37
 38    fn complete_argument(
 39        &self,
 40        query: String,
 41        _cancellation_flag: Arc<AtomicBool>,
 42        _workspace: WeakView<Workspace>,
 43        cx: &mut AppContext,
 44    ) -> Task<Result<Vec<String>>> {
 45        let store = self.store.clone();
 46        cx.background_executor().spawn(async move {
 47            let prompts = store.search(query).await;
 48            Ok(prompts
 49                .into_iter()
 50                .filter_map(|prompt| Some(prompt.title?.to_string()))
 51                .collect())
 52        })
 53    }
 54
 55    fn run(
 56        self: Arc<Self>,
 57        title: Option<&str>,
 58        _workspace: WeakView<Workspace>,
 59        _delegate: Arc<dyn LspAdapterDelegate>,
 60        cx: &mut WindowContext,
 61    ) -> Task<Result<SlashCommandOutput>> {
 62        let Some(title) = title else {
 63            return Task::ready(Err(anyhow!("missing prompt name")));
 64        };
 65
 66        let store = self.store.clone();
 67        let title = SharedString::from(title.to_string());
 68        let prompt = cx.background_executor().spawn({
 69            let title = title.clone();
 70            async move {
 71                let prompt_id = store
 72                    .id_for_title(&title)
 73                    .with_context(|| format!("no prompt found with title {:?}", title))?;
 74                let body = store.load(prompt_id).await?;
 75                anyhow::Ok(body)
 76            }
 77        });
 78        cx.foreground_executor().spawn(async move {
 79            let prompt = prompt.await?;
 80            let range = 0..prompt.len();
 81            Ok(SlashCommandOutput {
 82                text: prompt,
 83                sections: vec![SlashCommandOutputSection {
 84                    range,
 85                    render_placeholder: Arc::new(move |id, unfold, _cx| {
 86                        PromptPlaceholder {
 87                            id,
 88                            unfold,
 89                            title: title.clone(),
 90                        }
 91                        .into_any_element()
 92                    }),
 93                }],
 94            })
 95        })
 96    }
 97}
 98
 99#[derive(IntoElement)]
100pub struct PromptPlaceholder {
101    pub title: SharedString,
102    pub id: ElementId,
103    pub unfold: Arc<dyn Fn(&mut WindowContext)>,
104}
105
106impl RenderOnce for PromptPlaceholder {
107    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
108        let unfold = self.unfold;
109        ButtonLike::new(self.id)
110            .style(ButtonStyle::Filled)
111            .layer(ElevationIndex::ElevatedSurface)
112            .child(Icon::new(IconName::Library))
113            .child(Label::new(self.title))
114            .on_click(move |_, cx| unfold(cx))
115    }
116}