Skip to content

Commit 54fe7a4

Browse files
perf: lazily initialize Handlebars and SyntaxHighlighter to reduce startup time (#2739)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 298ef2b commit 54fe7a4

File tree

2 files changed

+21
-13
lines changed

2 files changed

+21
-13
lines changed

crates/forge_display/src/markdown.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::sync::OnceLock;
2+
13
use derive_setters::Setters;
24
use regex::Regex;
35
use termimad::crossterm::style::{Attribute, Color};
@@ -13,7 +15,7 @@ pub struct MarkdownFormat {
1315
skin: MadSkin,
1416
max_consecutive_newlines: usize,
1517
#[setters(skip)]
16-
highlighter: SyntaxHighlighter,
18+
highlighter: OnceLock<SyntaxHighlighter>,
1719
}
1820

1921
impl Default for MarkdownFormat {
@@ -39,7 +41,7 @@ impl MarkdownFormat {
3941
Self {
4042
skin,
4143
max_consecutive_newlines: 2,
42-
highlighter: SyntaxHighlighter::default(),
44+
highlighter: OnceLock::new(),
4345
}
4446
}
4547

@@ -55,10 +57,8 @@ impl MarkdownFormat {
5557

5658
// Render with termimad, then restore highlighted code
5759
let rendered = self.skin.term_text(processed.markdown()).to_string();
58-
processed
59-
.restore(&self.highlighter, rendered)
60-
.trim()
61-
.to_string()
60+
let highlighter = self.highlighter.get_or_init(SyntaxHighlighter::default);
61+
processed.restore(highlighter, rendered).trim().to_string()
6262
}
6363

6464
fn strip_excessive_newlines(&self, content: &str) -> String {

crates/forge_services/src/template.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,25 @@ use forge_app::{EnvironmentInfra, FileReaderInfra, TemplateService};
66
use forge_domain::Template;
77
use futures::future;
88
use handlebars::Handlebars;
9-
use tokio::sync::RwLock;
9+
use tokio::sync::{OnceCell, RwLock};
1010

1111
#[derive(Clone)]
1212
pub struct ForgeTemplateService<F> {
13-
hb: Arc<RwLock<Handlebars<'static>>>,
13+
hb: Arc<OnceCell<RwLock<Handlebars<'static>>>>,
1414
infra: Arc<F>,
1515
}
1616

1717
impl<F: EnvironmentInfra + FileReaderInfra> ForgeTemplateService<F> {
1818
pub fn new(infra: Arc<F>) -> Self {
19-
let hb = forge_app::TemplateEngine::handlebar_instance();
20-
Self { hb: Arc::new(RwLock::new(hb)), infra }
19+
Self { hb: Arc::new(OnceCell::new()), infra }
20+
}
21+
22+
/// Returns a reference to the lazily-initialized Handlebars RwLock,
23+
/// creating the instance on the first call.
24+
async fn get_hb(&self) -> &RwLock<Handlebars<'static>> {
25+
self.hb
26+
.get_or_init(|| async { RwLock::new(forge_app::TemplateEngine::handlebar_instance()) })
27+
.await
2128
}
2229

2330
/// Reads multiple template files in parallel and returns their names and
@@ -74,7 +81,7 @@ impl<F: EnvironmentInfra + FileReaderInfra> TemplateService for ForgeTemplateSer
7481
let cwd = &self.infra.get_environment().cwd;
7582

7683
// Discover and filter unregistered templates in one pass
77-
let guard = self.hb.read().await;
84+
let guard = self.get_hb().await.read().await;
7885
let path = if path.is_absolute() {
7986
path.to_string_lossy().to_string()
8087
} else {
@@ -98,7 +105,7 @@ impl<F: EnvironmentInfra + FileReaderInfra> TemplateService for ForgeTemplateSer
98105

99106
// Register all templates if any were found
100107
if !templates.is_empty() {
101-
let mut guard = self.hb.write().await;
108+
let mut guard = self.get_hb().await.write().await;
102109
for (name, content) in templates {
103110
let template = compile_template(&name, &content)?;
104111
guard.register_template(&name, template);
@@ -114,7 +121,8 @@ impl<F: EnvironmentInfra + FileReaderInfra> TemplateService for ForgeTemplateSer
114121
object: &V,
115122
) -> anyhow::Result<String> {
116123
let rendered = self
117-
.hb
124+
.get_hb()
125+
.await
118126
.read()
119127
.await
120128
.render_template(&template.template, object)?;

0 commit comments

Comments
 (0)