Start on a database-backed prompt library (#12468)

Using the file system as a database seems like it's easy, but it's
actually a real pain. I'd like to use LMDB to store the prompts locally
so we have more control. We can always add an export option, but I want
the source of truth to be somewhere other than the file system.

So far, I have a PromptStore which is global to the application and can
be initialized on startup. Then there's a `PromptLibrary` which is
intended to be the root of a new kind of Zed window. I haven't actually
seen pixels yet, but I've sketched out the basics needed to create a new
prompt, save, etc.

Still lots to figure out but the foundations of being backed by a DB and
rendering in an independent window are in place.

/cc @iamnbutler @as-cii 

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
Nathan Sobo 2024-06-03 07:58:43 -06:00 committed by GitHub
parent 18e2b43d6d
commit 5f98b9617a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 1427 additions and 1429 deletions

View file

@ -1,8 +1,7 @@
use super::{SlashCommand, SlashCommandOutput};
use crate::prompts::PromptLibrary;
use crate::prompt_library::PromptStore;
use anyhow::{anyhow, Context, Result};
use assistant_slash_command::SlashCommandOutputSection;
use fuzzy::StringMatchCandidate;
use gpui::{AppContext, Task, WeakView};
use language::LspAdapterDelegate;
use std::sync::{atomic::AtomicBool, Arc};
@ -10,12 +9,12 @@ use ui::{prelude::*, ButtonLike, ElevationIndex};
use workspace::Workspace;
pub(crate) struct PromptSlashCommand {
library: Arc<PromptLibrary>,
store: Arc<PromptStore>,
}
impl PromptSlashCommand {
pub fn new(library: Arc<PromptLibrary>) -> Self {
Self { library }
pub fn new(store: Arc<PromptStore>) -> Self {
Self { store }
}
}
@ -39,31 +38,16 @@ impl SlashCommand for PromptSlashCommand {
fn complete_argument(
&self,
query: String,
cancellation_flag: Arc<AtomicBool>,
_cancellation_flag: Arc<AtomicBool>,
_workspace: WeakView<Workspace>,
cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
let library = self.library.clone();
let executor = cx.background_executor().clone();
let store = self.store.clone();
cx.background_executor().spawn(async move {
let candidates = library
.prompts()
let prompts = store.search(query).await;
Ok(prompts
.into_iter()
.enumerate()
.map(|(ix, prompt)| StringMatchCandidate::new(ix, prompt.1.title().to_string()))
.collect::<Vec<_>>();
let matches = fuzzy::match_strings(
&candidates,
&query,
false,
100,
&cancellation_flag,
executor,
)
.await;
Ok(matches
.into_iter()
.map(|mat| candidates[mat.candidate_id].string.clone())
.filter_map(|prompt| Some(prompt.title?.to_string()))
.collect())
})
}
@ -79,19 +63,16 @@ impl SlashCommand for PromptSlashCommand {
return Task::ready(Err(anyhow!("missing prompt name")));
};
let library = self.library.clone();
let store = self.store.clone();
let title = SharedString::from(title.to_string());
let prompt = cx.background_executor().spawn({
let title = title.clone();
async move {
let prompt = library
.prompts()
.into_iter()
.map(|prompt| (prompt.1.title(), prompt))
.find(|(t, _)| t == &title)
.with_context(|| format!("no prompt found with title {:?}", title))?
.1;
anyhow::Ok(prompt.1.body())
let prompt_id = store
.id_for_title(&title)
.with_context(|| format!("no prompt found with title {:?}", title))?;
let body = store.load(prompt_id).await?;
anyhow::Ok(body)
}
});
cx.foreground_executor().spawn(async move {
@ -102,16 +83,34 @@ impl SlashCommand for PromptSlashCommand {
sections: vec![SlashCommandOutputSection {
range,
render_placeholder: Arc::new(move |id, unfold, _cx| {
ButtonLike::new(id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(IconName::Library))
.child(Label::new(title.clone()))
.on_click(move |_, cx| unfold(cx))
.into_any_element()
PromptPlaceholder {
id,
unfold,
title: title.clone(),
}
.into_any_element()
}),
}],
})
})
}
}
#[derive(IntoElement)]
pub struct PromptPlaceholder {
pub title: SharedString,
pub id: ElementId,
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
}
impl RenderOnce for PromptPlaceholder {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let unfold = self.unfold;
ButtonLike::new(self.id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(IconName::Library))
.child(Label::new(self.title))
.on_click(move |_, cx| unfold(cx))
}
}