tangled
alpha
login
or
join now
nonbinary.computer
/
weaver
atproto blogging
24
fork
atom
overview
issues
2
pulls
pipelines
fixed the stupid code block bug
Orual
1 month ago
0d35d8db
6ecfcf49
+89
-10
2 changed files
expand all
collapse all
unified
split
crates
weaver-app
src
components
editor
render.rs
writer.rs
+29
-2
crates/weaver-app/src/components/editor/render.rs
···
159
// Determine if we can use fast path (skip boundary discovery)
160
// Need cache and non-boundary-affecting edit info (for edit position)
161
let current_len = text.len_unicode();
162
-
let use_fast_path = cache.is_some() && edit.is_some() && !is_boundary_affecting(edit.unwrap());
0
0
0
163
164
tracing::debug!(
165
target: "weaver::render",
···
218
})
219
.collect::<Vec<_>>()
220
} else {
221
-
// Slow path: run boundary-only pass to discover paragraph boundaries
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
222
let parser =
223
Parser::new_ext(&source, weaver_renderer::default_md_options()).into_offset_iter();
224
let mut scratch_output = String::new();
···
234
Ok(result) => result.paragraph_ranges,
235
Err(_) => return (Vec::new(), RenderCache::default(), vec![]),
236
}
0
0
237
};
238
239
// Log discovered paragraphs
···
159
// Determine if we can use fast path (skip boundary discovery)
160
// Need cache and non-boundary-affecting edit info (for edit position)
161
let current_len = text.len_unicode();
162
+
163
+
let use_fast_path = cache.is_some()
164
+
&& edit.is_some()
165
+
&& !is_boundary_affecting(edit.unwrap());
166
167
tracing::debug!(
168
target: "weaver::render",
···
221
})
222
.collect::<Vec<_>>()
223
} else {
224
+
vec![] // Will be populated by slow path below
225
+
};
226
+
227
+
// Validate fast path results - if any ranges are invalid, use slow path
228
+
let paragraph_ranges = if !paragraph_ranges.is_empty() {
229
+
let all_valid = paragraph_ranges
230
+
.iter()
231
+
.all(|(_, char_range)| char_range.start <= char_range.end);
232
+
if all_valid {
233
+
paragraph_ranges
234
+
} else {
235
+
tracing::debug!(
236
+
target: "weaver::render",
237
+
"fast path produced invalid ranges, falling back to slow path"
238
+
);
239
+
vec![] // Trigger slow path
240
+
}
241
+
} else {
242
+
paragraph_ranges
243
+
};
244
+
245
+
// Slow path: run boundary-only pass to discover paragraph boundaries
246
+
let paragraph_ranges = if paragraph_ranges.is_empty() {
247
let parser =
248
Parser::new_ext(&source, weaver_renderer::default_md_options()).into_offset_iter();
249
let mut scratch_output = String::new();
···
259
Ok(result) => result.paragraph_ranges,
260
Err(_) => return (Vec::new(), RenderCache::default(), vec![]),
261
}
262
+
} else {
263
+
paragraph_ranges
264
};
265
266
// Log discovered paragraphs
+60
-8
crates/weaver-app/src/components/editor/writer.rs
···
338
code_buffer: Option<(Option<String>, String)>, // (lang, content)
339
code_buffer_byte_range: Option<Range<usize>>, // byte range of buffered code content
340
code_buffer_char_range: Option<Range<usize>>, // char range of buffered code content
0
0
341
pending_blockquote_range: Option<Range<usize>>, // range for emitting > inside next paragraph
342
343
// Table rendering mode
···
455
code_buffer: None,
456
code_buffer_byte_range: None,
457
code_buffer_char_range: None,
0
0
458
pending_blockquote_range: None,
459
render_tables_as_markdown: true, // Default to markdown rendering
460
table_start_offset: None,
···
503
code_buffer: None,
504
code_buffer_byte_range: None,
505
code_buffer_char_range: None,
0
0
506
pending_blockquote_range: None,
507
render_tables_as_markdown: true,
508
table_start_offset: None,
···
545
code_buffer: self.code_buffer,
546
code_buffer_byte_range: self.code_buffer_byte_range,
547
code_buffer_char_range: self.code_buffer_char_range,
0
0
548
pending_blockquote_range: self.pending_blockquote_range,
549
render_tables_as_markdown: self.render_tables_as_markdown,
550
table_start_offset: self.table_start_offset,
···
590
code_buffer: self.code_buffer,
591
code_buffer_byte_range: self.code_buffer_byte_range,
592
code_buffer_char_range: self.code_buffer_char_range,
0
0
593
pending_blockquote_range: self.pending_blockquote_range,
594
render_tables_as_markdown: self.render_tables_as_markdown,
595
table_start_offset: self.table_start_offset,
···
1973
escape_html(&mut self.writer, syntax)?;
1974
self.write("</span>\n")?;
1975
0
0
0
0
1976
self.syntax_spans.push(SyntaxSpanInfo {
1977
syn_id,
1978
char_range: char_start..char_end,
1979
syntax_type: SyntaxType::Block,
1980
-
formatted_range: None,
1981
});
1982
1983
self.last_char_offset += syntax_char_len;
···
2570
self.record_mapping(code_byte_range, code_char_range);
2571
}
2572
0
0
0
2573
if let Some(ref lang_str) = lang {
2574
// Use a temporary String buffer for syntect
2575
let mut temp_output = String::new();
···
2580
&mut temp_output,
2581
) {
2582
Ok(_) => {
2583
-
self.write(&temp_output)?;
0
0
0
0
0
0
0
0
0
0
2584
}
2585
Err(_) => {
2586
// Fallback to plain code block
2587
-
self.write("<pre><code class=\"language-")?;
0
0
0
0
2588
escape_html(&mut self.writer, lang_str)?;
2589
self.write("\">")?;
2590
escape_html_body_text(&mut self.writer, &buffer)?;
···
2592
}
2593
}
2594
} else {
2595
-
self.write("<pre><code>")?;
0
0
0
0
2596
escape_html_body_text(&mut self.writer, &buffer)?;
2597
self.write("</code></pre>\n")?;
2598
}
···
2604
}
2605
2606
// Emit closing ``` (emit_gap_before is skipped while buffering)
0
0
0
0
2607
if range.start < range.end {
2608
let raw_text = &self.source[range.clone()];
2609
if let Some(fence_line) = raw_text.lines().last() {
···
2623
escape_html(&mut self.writer, fence)?;
2624
self.write("</span>")?;
2625
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
2626
self.syntax_spans.push(SyntaxSpanInfo {
2627
syn_id,
2628
char_range: char_start..char_end,
2629
syntax_type: SyntaxType::Block,
2630
-
formatted_range: None,
2631
});
2632
-
2633
-
self.last_char_offset += fence_char_len;
2634
-
self.last_byte_offset += fence.len();
2635
}
2636
}
2637
}
···
338
code_buffer: Option<(Option<String>, String)>, // (lang, content)
339
code_buffer_byte_range: Option<Range<usize>>, // byte range of buffered code content
340
code_buffer_char_range: Option<Range<usize>>, // char range of buffered code content
341
+
code_block_char_start: Option<usize>, // char offset where code block started
342
+
code_block_opening_span_idx: Option<usize>, // index of opening fence syntax span
343
pending_blockquote_range: Option<Range<usize>>, // range for emitting > inside next paragraph
344
345
// Table rendering mode
···
457
code_buffer: None,
458
code_buffer_byte_range: None,
459
code_buffer_char_range: None,
460
+
code_block_char_start: None,
461
+
code_block_opening_span_idx: None,
462
pending_blockquote_range: None,
463
render_tables_as_markdown: true, // Default to markdown rendering
464
table_start_offset: None,
···
507
code_buffer: None,
508
code_buffer_byte_range: None,
509
code_buffer_char_range: None,
510
+
code_block_char_start: None,
511
+
code_block_opening_span_idx: None,
512
pending_blockquote_range: None,
513
render_tables_as_markdown: true,
514
table_start_offset: None,
···
551
code_buffer: self.code_buffer,
552
code_buffer_byte_range: self.code_buffer_byte_range,
553
code_buffer_char_range: self.code_buffer_char_range,
554
+
code_block_char_start: self.code_block_char_start,
555
+
code_block_opening_span_idx: self.code_block_opening_span_idx,
556
pending_blockquote_range: self.pending_blockquote_range,
557
render_tables_as_markdown: self.render_tables_as_markdown,
558
table_start_offset: self.table_start_offset,
···
598
code_buffer: self.code_buffer,
599
code_buffer_byte_range: self.code_buffer_byte_range,
600
code_buffer_char_range: self.code_buffer_char_range,
601
+
code_block_char_start: self.code_block_char_start,
602
+
code_block_opening_span_idx: self.code_block_opening_span_idx,
603
pending_blockquote_range: self.pending_blockquote_range,
604
render_tables_as_markdown: self.render_tables_as_markdown,
605
table_start_offset: self.table_start_offset,
···
1983
escape_html(&mut self.writer, syntax)?;
1984
self.write("</span>\n")?;
1985
1986
+
// Track opening span index for formatted_range update later
1987
+
self.code_block_opening_span_idx = Some(self.syntax_spans.len());
1988
+
self.code_block_char_start = Some(char_start);
1989
+
1990
self.syntax_spans.push(SyntaxSpanInfo {
1991
syn_id,
1992
char_range: char_start..char_end,
1993
syntax_type: SyntaxType::Block,
1994
+
formatted_range: None, // Will be set in TagEnd::CodeBlock
1995
});
1996
1997
self.last_char_offset += syntax_char_len;
···
2584
self.record_mapping(code_byte_range, code_char_range);
2585
}
2586
2587
+
// Get node_id for data-node-id attribute (needed for cursor positioning)
2588
+
let node_id = self.current_node_id.clone();
2589
+
2590
if let Some(ref lang_str) = lang {
2591
// Use a temporary String buffer for syntect
2592
let mut temp_output = String::new();
···
2597
&mut temp_output,
2598
) {
2599
Ok(_) => {
2600
+
// Inject data-node-id into the <pre> tag for cursor positioning
2601
+
if let Some(ref nid) = node_id {
2602
+
let injected = temp_output.replacen(
2603
+
"<pre>",
2604
+
&format!("<pre data-node-id=\"{}\">", nid),
2605
+
1,
2606
+
);
2607
+
self.write(&injected)?;
2608
+
} else {
2609
+
self.write(&temp_output)?;
2610
+
}
2611
}
2612
Err(_) => {
2613
// Fallback to plain code block
2614
+
if let Some(ref nid) = node_id {
2615
+
write!(&mut self.writer, "<pre data-node-id=\"{}\"><code class=\"language-", nid)?;
2616
+
} else {
2617
+
self.write("<pre><code class=\"language-")?;
2618
+
}
2619
escape_html(&mut self.writer, lang_str)?;
2620
self.write("\">")?;
2621
escape_html_body_text(&mut self.writer, &buffer)?;
···
2623
}
2624
}
2625
} else {
2626
+
if let Some(ref nid) = node_id {
2627
+
write!(&mut self.writer, "<pre data-node-id=\"{}\"><code>", nid)?;
2628
+
} else {
2629
+
self.write("<pre><code>")?;
2630
+
}
2631
escape_html_body_text(&mut self.writer, &buffer)?;
2632
self.write("</code></pre>\n")?;
2633
}
···
2639
}
2640
2641
// Emit closing ``` (emit_gap_before is skipped while buffering)
2642
+
// Track the opening span index and char start before we potentially clear them
2643
+
let opening_span_idx = self.code_block_opening_span_idx.take();
2644
+
let code_block_start = self.code_block_char_start.take();
2645
+
2646
if range.start < range.end {
2647
let raw_text = &self.source[range.clone()];
2648
if let Some(fence_line) = raw_text.lines().last() {
···
2662
escape_html(&mut self.writer, fence)?;
2663
self.write("</span>")?;
2664
2665
+
self.last_char_offset += fence_char_len;
2666
+
self.last_byte_offset += fence.len();
2667
+
2668
+
// Compute formatted_range for entire code block (opening fence to closing fence)
2669
+
let formatted_range = code_block_start
2670
+
.map(|start| start..self.last_char_offset);
2671
+
2672
+
// Update opening fence span with formatted_range
2673
+
if let (Some(idx), Some(fr)) = (opening_span_idx, formatted_range.as_ref())
2674
+
{
2675
+
if let Some(span) = self.syntax_spans.get_mut(idx) {
2676
+
span.formatted_range = Some(fr.clone());
2677
+
}
2678
+
}
2679
+
2680
+
// Push closing fence span with formatted_range
2681
self.syntax_spans.push(SyntaxSpanInfo {
2682
syn_id,
2683
char_range: char_start..char_end,
2684
syntax_type: SyntaxType::Block,
2685
+
formatted_range,
2686
});
0
0
0
2687
}
2688
}
2689
}