1use anyhow::Result;
2use gpui::SharedString;
3use handlebars::Handlebars;
4use rust_embed::RustEmbed;
5use serde::Serialize;
6use std::sync::Arc;
7
8#[derive(RustEmbed)]
9#[folder = "src/templates"]
10#[include = "*.hbs"]
11struct Assets;
12
13pub struct Templates(Handlebars<'static>);
14
15impl Templates {
16 pub fn new() -> Arc<Self> {
17 let mut handlebars = Handlebars::new();
18 handlebars.set_strict_mode(true);
19 handlebars.register_helper("contains", Box::new(contains));
20 handlebars.register_embed_templates::<Assets>().unwrap();
21 Arc::new(Self(handlebars))
22 }
23}
24
25pub trait Template: Sized {
26 const TEMPLATE_NAME: &'static str;
27
28 fn render(&self, templates: &Templates) -> Result<String>
29 where
30 Self: Serialize + Sized,
31 {
32 Ok(templates.0.render(Self::TEMPLATE_NAME, self)?)
33 }
34}
35
36#[expect(
37 dead_code,
38 reason = "Marked as unused by Rust 1.89 and left as is as of 07 Aug 2025 to let AI team address it."
39)]
40#[derive(Serialize)]
41pub struct GlobTemplate {
42 pub project_roots: String,
43}
44
45impl Template for GlobTemplate {
46 const TEMPLATE_NAME: &'static str = "glob.hbs";
47}
48
49#[derive(Serialize)]
50pub struct SystemPromptTemplate<'a> {
51 #[serde(flatten)]
52 pub project: &'a prompt_store::ProjectContext,
53 pub available_tools: Vec<SharedString>,
54}
55
56impl Template for SystemPromptTemplate<'_> {
57 const TEMPLATE_NAME: &'static str = "system_prompt.hbs";
58}
59
60/// Handlebars helper for checking if an item is in a list
61fn contains(
62 h: &handlebars::Helper,
63 _: &handlebars::Handlebars,
64 _: &handlebars::Context,
65 _: &mut handlebars::RenderContext,
66 out: &mut dyn handlebars::Output,
67) -> handlebars::HelperResult {
68 let list = h
69 .param(0)
70 .and_then(|v| v.value().as_array())
71 .ok_or_else(|| {
72 handlebars::RenderError::new("contains: missing or invalid list parameter")
73 })?;
74 let query = h.param(1).map(|v| v.value()).ok_or_else(|| {
75 handlebars::RenderError::new("contains: missing or invalid query parameter")
76 })?;
77
78 if list.contains(&query) {
79 out.write("true")?;
80 }
81
82 Ok(())
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_system_prompt_template() {
91 let project = prompt_store::ProjectContext::default();
92 let template = SystemPromptTemplate {
93 project: &project,
94 available_tools: vec!["echo".into()],
95 };
96 let templates = Templates::new();
97 let rendered = template.render(&templates).unwrap();
98 assert!(rendered.contains("## Fixing Diagnostics"));
99 }
100}