prompt_command.rs

  1use super::{SlashCommand, SlashCommandOutput};
  2use crate::prompt_library::PromptStore;
  3use anyhow::{anyhow, Context, Result};
  4use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
  5use gpui::{Task, WeakView};
  6use language::LspAdapterDelegate;
  7use std::sync::{atomic::AtomicBool, Arc};
  8use ui::prelude::*;
  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: Arc<Self>,
 32        arguments: &[String],
 33        _cancellation_flag: Arc<AtomicBool>,
 34        _workspace: Option<WeakView<Workspace>>,
 35        cx: &mut WindowContext,
 36    ) -> Task<Result<Vec<ArgumentCompletion>>> {
 37        let store = PromptStore::global(cx);
 38        let query = arguments.to_owned().join(" ");
 39        cx.background_executor().spawn(async move {
 40            let prompts = store.await?.search(query).await;
 41            Ok(prompts
 42                .into_iter()
 43                .filter_map(|prompt| {
 44                    let prompt_title = prompt.title?.to_string();
 45                    Some(ArgumentCompletion {
 46                        label: prompt_title.clone().into(),
 47                        new_text: prompt_title,
 48                        run_command: true,
 49                        replace_previous_arguments: true,
 50                    })
 51                })
 52                .collect())
 53        })
 54    }
 55
 56    fn run(
 57        self: Arc<Self>,
 58        arguments: &[String],
 59        _workspace: WeakView<Workspace>,
 60        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
 61        cx: &mut WindowContext,
 62    ) -> Task<Result<SlashCommandOutput>> {
 63        let title = arguments.to_owned().join(" ");
 64        if title.trim().is_empty() {
 65            return Task::ready(Err(anyhow!("missing prompt name")));
 66        };
 67
 68        let store = PromptStore::global(cx);
 69        let title = SharedString::from(title.clone());
 70        let prompt = cx.background_executor().spawn({
 71            let title = title.clone();
 72            async move {
 73                let store = store.await?;
 74                let prompt_id = store
 75                    .id_for_title(&title)
 76                    .with_context(|| format!("no prompt found with title {:?}", title))?;
 77                let body = store.load(prompt_id).await?;
 78                anyhow::Ok(body)
 79            }
 80        });
 81        cx.foreground_executor().spawn(async move {
 82            let mut prompt = prompt.await?;
 83
 84            if prompt.starts_with('/') {
 85                // Prevent an edge case where the inserted prompt starts with a slash command (that leads to funky rendering).
 86                prompt.insert(0, '\n');
 87            }
 88            if prompt.is_empty() {
 89                prompt.push('\n');
 90            }
 91            let range = 0..prompt.len();
 92            Ok(SlashCommandOutput {
 93                text: prompt,
 94                sections: vec![SlashCommandOutputSection {
 95                    range,
 96                    icon: IconName::Library,
 97                    label: title,
 98                }],
 99                run_commands_in_text: true,
100            })
101        })
102    }
103}