prompt_command.rs

  1use crate::prompt_library::PromptStore;
  2use anyhow::{anyhow, Context, Result};
  3use assistant_slash_command::{
  4    ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
  5    SlashCommandResult,
  6};
  7use gpui::{Task, WeakView};
  8use language::{BufferSnapshot, LspAdapterDelegate};
  9use std::sync::{atomic::AtomicBool, Arc};
 10use ui::prelude::*;
 11use workspace::Workspace;
 12
 13pub(crate) struct PromptSlashCommand;
 14
 15impl SlashCommand for PromptSlashCommand {
 16    fn name(&self) -> String {
 17        "prompt".into()
 18    }
 19
 20    fn description(&self) -> String {
 21        "Insert prompt from library".into()
 22    }
 23
 24    fn menu_text(&self) -> String {
 25        self.description()
 26    }
 27
 28    fn requires_argument(&self) -> bool {
 29        true
 30    }
 31
 32    fn complete_argument(
 33        self: Arc<Self>,
 34        arguments: &[String],
 35        _cancellation_flag: Arc<AtomicBool>,
 36        _workspace: Option<WeakView<Workspace>>,
 37        cx: &mut WindowContext,
 38    ) -> Task<Result<Vec<ArgumentCompletion>>> {
 39        let store = PromptStore::global(cx);
 40        let query = arguments.to_owned().join(" ");
 41        cx.background_executor().spawn(async move {
 42            let prompts = store.await?.search(query).await;
 43            Ok(prompts
 44                .into_iter()
 45                .filter_map(|prompt| {
 46                    let prompt_title = prompt.title?.to_string();
 47                    Some(ArgumentCompletion {
 48                        label: prompt_title.clone().into(),
 49                        new_text: prompt_title,
 50                        after_completion: true.into(),
 51                        replace_previous_arguments: true,
 52                    })
 53                })
 54                .collect())
 55        })
 56    }
 57
 58    fn run(
 59        self: Arc<Self>,
 60        arguments: &[String],
 61        _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
 62        _context_buffer: BufferSnapshot,
 63        _workspace: WeakView<Workspace>,
 64        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
 65        cx: &mut WindowContext,
 66    ) -> Task<SlashCommandResult> {
 67        let title = arguments.to_owned().join(" ");
 68        if title.trim().is_empty() {
 69            return Task::ready(Err(anyhow!("missing prompt name")));
 70        };
 71
 72        let store = PromptStore::global(cx);
 73        let title = SharedString::from(title.clone());
 74        let prompt = cx.background_executor().spawn({
 75            let title = title.clone();
 76            async move {
 77                let store = store.await?;
 78                let prompt_id = store
 79                    .id_for_title(&title)
 80                    .with_context(|| format!("no prompt found with title {:?}", title))?;
 81                let body = store.load(prompt_id).await?;
 82                anyhow::Ok(body)
 83            }
 84        });
 85        cx.foreground_executor().spawn(async move {
 86            let mut prompt = prompt.await?;
 87
 88            if prompt.starts_with('/') {
 89                // Prevent an edge case where the inserted prompt starts with a slash command (that leads to funky rendering).
 90                prompt.insert(0, '\n');
 91            }
 92            if prompt.is_empty() {
 93                prompt.push('\n');
 94            }
 95            let range = 0..prompt.len();
 96            Ok(SlashCommandOutput {
 97                text: prompt,
 98                sections: vec![SlashCommandOutputSection {
 99                    range,
100                    icon: IconName::Library,
101                    label: title,
102                    metadata: None,
103                }],
104                run_commands_in_text: true,
105            }
106            .to_event_stream())
107        })
108    }
109}