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