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