diff --git a/crates/forge_display/src/markdown.rs b/crates/forge_display/src/markdown.rs index 52f089a929..43e2d7bf90 100644 --- a/crates/forge_display/src/markdown.rs +++ b/crates/forge_display/src/markdown.rs @@ -1,3 +1,5 @@ +use std::sync::OnceLock; + use derive_setters::Setters; use regex::Regex; use termimad::crossterm::style::{Attribute, Color}; @@ -13,7 +15,7 @@ pub struct MarkdownFormat { skin: MadSkin, max_consecutive_newlines: usize, #[setters(skip)] - highlighter: SyntaxHighlighter, + highlighter: OnceLock, } impl Default for MarkdownFormat { @@ -39,7 +41,7 @@ impl MarkdownFormat { Self { skin, max_consecutive_newlines: 2, - highlighter: SyntaxHighlighter::default(), + highlighter: OnceLock::new(), } } @@ -55,10 +57,8 @@ impl MarkdownFormat { // Render with termimad, then restore highlighted code let rendered = self.skin.term_text(processed.markdown()).to_string(); - processed - .restore(&self.highlighter, rendered) - .trim() - .to_string() + let highlighter = self.highlighter.get_or_init(SyntaxHighlighter::default); + processed.restore(highlighter, rendered).trim().to_string() } fn strip_excessive_newlines(&self, content: &str) -> String { diff --git a/crates/forge_services/src/template.rs b/crates/forge_services/src/template.rs index 3f04daa3c0..19a63c357a 100644 --- a/crates/forge_services/src/template.rs +++ b/crates/forge_services/src/template.rs @@ -6,18 +6,25 @@ use forge_app::{EnvironmentInfra, FileReaderInfra, TemplateService}; use forge_domain::Template; use futures::future; use handlebars::Handlebars; -use tokio::sync::RwLock; +use tokio::sync::{OnceCell, RwLock}; #[derive(Clone)] pub struct ForgeTemplateService { - hb: Arc>>, + hb: Arc>>>, infra: Arc, } impl ForgeTemplateService { pub fn new(infra: Arc) -> Self { - let hb = forge_app::TemplateEngine::handlebar_instance(); - Self { hb: Arc::new(RwLock::new(hb)), infra } + Self { hb: Arc::new(OnceCell::new()), infra } + } + + /// Returns a reference to the lazily-initialized Handlebars RwLock, + /// creating the instance on the first call. + async fn get_hb(&self) -> &RwLock> { + self.hb + .get_or_init(|| async { RwLock::new(forge_app::TemplateEngine::handlebar_instance()) }) + .await } /// Reads multiple template files in parallel and returns their names and @@ -74,7 +81,7 @@ impl TemplateService for ForgeTemplateSer let cwd = &self.infra.get_environment().cwd; // Discover and filter unregistered templates in one pass - let guard = self.hb.read().await; + let guard = self.get_hb().await.read().await; let path = if path.is_absolute() { path.to_string_lossy().to_string() } else { @@ -98,7 +105,7 @@ impl TemplateService for ForgeTemplateSer // Register all templates if any were found if !templates.is_empty() { - let mut guard = self.hb.write().await; + let mut guard = self.get_hb().await.write().await; for (name, content) in templates { let template = compile_template(&name, &content)?; guard.register_template(&name, template); @@ -114,7 +121,8 @@ impl TemplateService for ForgeTemplateSer object: &V, ) -> anyhow::Result { let rendered = self - .hb + .get_hb() + .await .read() .await .render_template(&template.template, object)?;