11use anyhow:: Result ;
2- use pulldown_cmark:: { Event , html, Parser , Tag , TagEnd , CodeBlockKind } ;
2+ use pulldown_cmark:: { Event , html, Tag , TagEnd , CowStr } ;
33use crate :: renderer:: { DocumentRenderer , DocumentMetadata } ;
44use crate :: syntax:: highlighter:: SyntaxHighlighter ;
55
@@ -9,75 +9,66 @@ impl HtmlRenderer {
99 pub fn new ( ) -> Self {
1010 Self
1111 }
12-
13- fn process_markdown_with_syntax_highlighting ( & self , markdown : & str ) -> Result < String > {
14- let parser = Parser :: new ( markdown) ;
15- let mut events = Vec :: new ( ) ;
16- let mut in_code_block = false ;
17- let mut code_lang = String :: new ( ) ;
18- let mut code_content = String :: new ( ) ;
19-
12+ }
13+
14+ impl DocumentRenderer for HtmlRenderer {
15+ fn render ( & self , events : Vec < Event > , metadata : & DocumentMetadata ) -> Result < Vec < u8 > > {
2016 // Initialize syntax highlighter
21- let highlighter = SyntaxHighlighter :: new ( )
22- . map_err ( |e| anyhow:: anyhow!( "Failed to create syntax highlighter: {}" , e) ) ?;
17+ let highlighter = SyntaxHighlighter :: new ( ) ?;
2318
24- for event in parser {
25- match event {
26- Event :: Start ( Tag :: CodeBlock ( CodeBlockKind :: Fenced ( lang) ) ) => {
27- in_code_block = true ;
28- code_lang = lang. to_string ( ) ;
29- code_content. clear ( ) ;
30- }
31- Event :: End ( TagEnd :: CodeBlock ) => {
32- if in_code_block {
33- // Apply syntax highlighting
34- let highlighted = if !code_lang. is_empty ( ) {
35- highlighter. highlight_to_html ( & code_content, Some ( & code_lang) )
36- } else {
37- code_content. replace ( '&' , "&" ) . replace ( '<' , "<" ) . replace ( '>' , ">" )
38- } ;
39-
40- // Add syntax highlighting CSS classes and wrap in proper HTML
41- let html_block = format ! (
42- r#"<div class="highlight"><pre class="code-block language-{}"><code class="language-{}">{}</code></pre></div>"# ,
43- code_lang, code_lang, highlighted
44- ) ;
45- events. push ( Event :: Html ( html_block. into ( ) ) ) ;
46-
47- in_code_block = false ;
19+ // Process events to add syntax highlighting
20+ let mut processed_events = Vec :: new ( ) ;
21+ let mut i = 0 ;
22+
23+ while i < events. len ( ) {
24+ match & events[ i] {
25+ Event :: Start ( Tag :: CodeBlock ( kind) ) => {
26+ // Extract language from code block
27+ let language = match kind {
28+ pulldown_cmark:: CodeBlockKind :: Fenced ( lang) => {
29+ if lang. is_empty ( ) { None } else { Some ( lang. as_ref ( ) ) }
30+ }
31+ _ => None ,
32+ } ;
33+
34+ // Find the corresponding text and end events
35+ i += 1 ;
36+ let mut code_content = String :: new ( ) ;
37+ while i < events. len ( ) {
38+ match & events[ i] {
39+ Event :: Text ( text) => {
40+ code_content. push_str ( text) ;
41+ }
42+ Event :: End ( TagEnd :: CodeBlock ) => {
43+ break ;
44+ }
45+ _ => { }
46+ }
47+ i += 1 ;
48+ }
49+
50+ // Generate highlighted HTML
51+ if language. is_some ( ) {
52+ let highlighted_html = highlighter. highlight_to_html ( & code_content, language) ;
53+ let wrapped_html = format ! ( "<pre>{}</pre>" , highlighted_html) ;
54+ processed_events. push ( Event :: Html ( CowStr :: Boxed ( wrapped_html. into_boxed_str ( ) ) ) ) ;
55+ } else {
56+ // No language specified, use regular code block
57+ processed_events. push ( Event :: Start ( Tag :: CodeBlock ( kind. clone ( ) ) ) ) ;
58+ processed_events. push ( Event :: Text ( CowStr :: Boxed ( code_content. into_boxed_str ( ) ) ) ) ;
59+ processed_events. push ( Event :: End ( TagEnd :: CodeBlock ) ) ;
4860 }
49- }
50- Event :: Text ( text) if in_code_block => {
51- code_content. push_str ( & text) ;
5261 }
5362 _ => {
54- if !in_code_block {
55- events. push ( event) ;
56- }
63+ processed_events. push ( events[ i] . clone ( ) ) ;
5764 }
5865 }
66+ i += 1 ;
5967 }
6068
61- let mut html_output = String :: new ( ) ;
62- html:: push_html ( & mut html_output, events. into_iter ( ) ) ;
63-
64- Ok ( html_output)
65- }
66- }
67-
68- impl DocumentRenderer for HtmlRenderer {
69- fn render ( & self , events : Vec < Event > , metadata : & DocumentMetadata ) -> Result < Vec < u8 > > {
70- // Convert markdown events to HTML body
69+ // Convert processed events to HTML
7170 let mut body_html = String :: new ( ) ;
72- html:: push_html ( & mut body_html, events. into_iter ( ) ) ;
73-
74- // Replace \newpage with CSS page break (handle different markdown outputs)
75- body_html = body_html. replace ( "<p>\\ newpage</p>" , r#"<div style="page-break-before: always;"></div>"# ) ;
76- body_html = body_html. replace ( "\\ newpage" , r#"<div style="page-break-before: always;"></div>"# ) ;
77-
78- fn render_markdown ( & self , markdown : & str , metadata : & DocumentMetadata ) -> Result < Vec < u8 > > {
79- // Process markdown with syntax highlighting
80- let mut body_html = self . process_markdown_with_syntax_highlighting ( markdown) ?;
71+ html:: push_html ( & mut body_html, processed_events. into_iter ( ) ) ;
8172
8273 // Replace \newpage with CSS page break (handle different markdown outputs)
8374 body_html = body_html. replace ( "<p>\\ newpage</p>" , r#"<div style="page-break-before: always;"></div>"# ) ;
@@ -181,6 +172,19 @@ impl DocumentRenderer for HtmlRenderer {
181172 border-bottom: 1px solid #e1e4e8;
182173 }}
183174
175+ /* Syntect syntax highlighting styles */
176+ .source {{ background-color: transparent; }}
177+ [class*="comment"] {{ color: #6a737d; font-style: italic; }}
178+ [class*="keyword"] {{ color: #d73a49; font-weight: bold; }}
179+ [class*="storage"] {{ color: #d73a49; font-weight: bold; }}
180+ [class*="string"] {{ color: #032f62; }}
181+ [class*="constant"] {{ color: #005cc5; }}
182+ [class*="entity"] {{ color: #6f42c1; }}
183+ [class*="support"] {{ color: #005cc5; }}
184+ [class*="variable"] {{ color: #e36209; }}
185+ [class*="punctuation"] {{ color: #24292e; }}
186+ [class*="meta"] {{ color: #24292e; }}
187+
184188 @media (prefers-color-scheme: dark) {{
185189 body {{
186190 background-color: #0d1117;
@@ -221,51 +225,18 @@ impl DocumentRenderer for HtmlRenderer {
221225 color: #8b949e;
222226 border-bottom-color: #30363d;
223227 }}
224- }}
225-
226- /* Syntax highlighting styles */
227- .highlight {{
228- background-color: #f8f9fa;
229- border-radius: 6px;
230- overflow-x: auto;
231- }}
232-
233- .code-block {{
234- background-color: transparent;
235- padding: 1rem;
236- margin: 0;
237- font-family: 'SF Mono', Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
238- font-size: 0.875rem;
239- line-height: 1.45;
240- }}
241-
242- /* Syntect CSS classes for syntax highlighting */
243- .syntect-keyword {{ color: #d73a49; font-weight: bold; }}
244- .syntect-string {{ color: #032f62; }}
245- .syntect-comment {{ color: #6f42c1; font-style: italic; }}
246- .syntect-function {{ color: #6f42c1; }}
247- .syntect-type {{ color: #005cc5; }}
248- .syntect-number {{ color: #005cc5; }}
249- .syntect-constant {{ color: #005cc5; }}
250- .syntect-variable {{ color: #e36209; }}
251- .syntect-operator {{ color: #d73a49; }}
252- .syntect-preprocessor {{ color: #735c0f; }}
253-
254- @media (prefers-color-scheme: dark) {{
255- .highlight {{
256- background-color: #161b22;
257- }}
258228
259- .syntect-keyword {{ color: #ff7b72; }}
260- .syntect-string {{ color: #a5d6ff; }}
261- .syntect-comment {{ color: #8b949e; }}
262- .syntect-function {{ color: #d2a8ff; }}
263- .syntect-type {{ color: #79c0ff; }}
264- .syntect-number {{ color: #79c0ff; }}
265- .syntect-constant {{ color: #79c0ff; }}
266- .syntect-variable {{ color: #ffa657; }}
267- .syntect-operator {{ color: #ff7b72; }}
268- .syntect-preprocessor {{ color: #e3b341; }}
229+ /* Dark mode syntax highlighting */
230+ [class*="comment"] {{ color: #8b949e; }}
231+ [class*="keyword"] {{ color: #ff7b72; }}
232+ [class*="storage"] {{ color: #ff7b72; }}
233+ [class*="string"] {{ color: #a5d6ff; }}
234+ [class*="constant"] {{ color: #79c0ff; }}
235+ [class*="entity"] {{ color: #d2a8ff; }}
236+ [class*="support"] {{ color: #79c0ff; }}
237+ [class*="variable"] {{ color: #ffa657; }}
238+ [class*="punctuation"] {{ color: #c9d1d9; }}
239+ [class*="meta"] {{ color: #c9d1d9; }}
269240 }}
270241 </style>
271242</head>
0 commit comments