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::{BufferSnapshot, 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                        after_completion: true.into(),
 49                        replace_previous_arguments: true,
 50                    })
 51                })
 52                .collect())
 53        })
 54    }
 55
 56    fn run(
 57        self: Arc<Self>,
 58        arguments: &[String],
 59        _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
 60        _context_buffer: BufferSnapshot,
 61        _workspace: WeakView<Workspace>,
 62        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
 63        cx: &mut WindowContext,
 64    ) -> Task<Result<SlashCommandOutput>> {
 65        let title = arguments.to_owned().join(" ");
 66        if title.trim().is_empty() {
 67            return Task::ready(Err(anyhow!("missing prompt name")));
 68        };
 69
 70        let store = PromptStore::global(cx);
 71        let title = SharedString::from(title.clone());
 72        let prompt = cx.background_executor().spawn({
 73            let title = title.clone();
 74            async move {
 75                let store = store.await?;
 76                let prompt_id = store
 77                    .id_for_title(&title)
 78                    .with_context(|| format!("no prompt found with title {:?}", title))?;
 79                let body = store.load(prompt_id).await?;
 80                anyhow::Ok(body)
 81            }
 82        });
 83        cx.foreground_executor().spawn(async move {
 84            let mut prompt = prompt.await?;
 85
 86            if prompt.starts_with('/') {
 87                // Prevent an edge case where the inserted prompt starts with a slash command (that leads to funky rendering).
 88                prompt.insert(0, '\n');
 89            }
 90            if prompt.is_empty() {
 91                prompt.push('\n');
 92            }
 93            let range = 0..prompt.len();
 94            Ok(SlashCommandOutput {
 95                text: prompt,
 96                sections: vec![SlashCommandOutputSection {
 97                    range,
 98                    icon: IconName::Library,
 99                    label: title,
100                    metadata: None,
101                }],
102                run_commands_in_text: true,
103            })
104        })
105    }
106}