@@ -44,6 +44,7 @@ fn markdown_to_blocks(source string, style MarkdownStyle) []MarkdownBlock {
4444
4545 for i < lines.len {
4646 line := lines[i]
47+ trimmed := line.trim_space ()
4748
4849 // Handle code blocks
4950 if line.starts_with ('```' ) {
@@ -87,7 +88,7 @@ fn markdown_to_blocks(source string, style MarkdownStyle) []MarkdownBlock {
8788 }
8889
8990 // Horizontal rule
90- if line. trim_space () in ['---' , '***' , '___' ] {
91+ if trimmed in ['---' , '***' , '___' ] {
9192 // Flush current runs first
9293 if block := flush_runs (mut runs) {
9394 blocks << block
@@ -101,7 +102,7 @@ fn markdown_to_blocks(source string, style MarkdownStyle) []MarkdownBlock {
101102 }
102103
103104 // Blank line = paragraph break
104- if line. trim_space () == '' {
105+ if trimmed == '' {
105106 if runs.len > 0 {
106107 runs << rich_br ()
107108 }
@@ -116,13 +117,13 @@ fn markdown_to_blocks(source string, style MarkdownStyle) []MarkdownBlock {
116117 }
117118
118119 // Table recognition: lines starting with | or separator rows
119- if line. trim_space (). starts_with ('|' ) || is_table_separator (line ) {
120+ if trimmed. starts_with ('|' ) || is_table_separator (trimmed ) {
120121 // Flush current runs
121122 if block := flush_runs (mut runs) {
122123 blocks << block
123124 }
124125 // Collect consecutive table lines
125- mut table_lines := []string {}
126+ mut table_lines := []string {cap: 10 }
126127 for i < lines.len {
127128 tl := lines[i].trim_space ()
128129 if tl.starts_with ('|' ) || is_table_separator (tl) || tl.contains ('|' ) {
@@ -152,8 +153,7 @@ fn markdown_to_blocks(source string, style MarkdownStyle) []MarkdownBlock {
152153 }
153154
154155 // Definition list: line starting with : (treat as paragraph for now)
155- if line.trim_space ().starts_with (':' ) && line.trim_space ().len > 1
156- && line.trim_space ()[1 ] == ` ` {
156+ if trimmed.len > 1 && trimmed[0 ] == `:` && trimmed[1 ] == ` ` {
157157 // Treat as regular paragraph
158158 parse_inline (line, style.text, style, mut runs)
159159 runs << rich_br ()
@@ -190,7 +190,7 @@ fn markdown_to_blocks(source string, style MarkdownStyle) []MarkdownBlock {
190190 }
191191 // Count initial depth and collect consecutive blockquote lines
192192 mut max_depth := count_blockquote_depth (line)
193- mut quote_lines := []string {}
193+ mut quote_lines := []string {cap: 10 }
194194 for i < lines.len {
195195 q := lines[i]
196196 if q.starts_with ('>' ) {
@@ -206,7 +206,7 @@ fn markdown_to_blocks(source string, style MarkdownStyle) []MarkdownBlock {
206206 break
207207 }
208208 }
209- mut quote_runs := []RichTextRun{}
209+ mut quote_runs := []RichTextRun{cap: 20 }
210210 for qi, ql in quote_lines {
211211 // Skip blank lines but keep them as line breaks
212212 if ql.trim_space () == '' {
@@ -271,16 +271,16 @@ fn markdown_to_blocks(source string, style MarkdownStyle) []MarkdownBlock {
271271 }
272272
273273 // List items
274- trimmed := line.trim_left (' \t ' )
274+ left_trimmed := line.trim_left (' \t ' )
275275 indent := get_indent_level (line)
276276
277277 // Task list (checked or unchecked)
278- if task_prefix := get_task_prefix (trimmed ) {
278+ if task_prefix := get_task_prefix (left_trimmed ) {
279279 if block := flush_runs (mut runs) {
280280 blocks << block
281281 }
282- content , consumed := collect_list_item_content (trimmed [6 ..], lines, i + 1 )
283- mut item_runs := []RichTextRun{}
282+ content , consumed := collect_list_item_content (left_trimmed [6 ..], lines, i + 1 )
283+ mut item_runs := []RichTextRun{cap: 10 }
284284 parse_inline (content, style.text, style, mut item_runs)
285285 blocks << MarkdownBlock{
286286 is_list: true
@@ -295,13 +295,14 @@ fn markdown_to_blocks(source string, style MarkdownStyle) []MarkdownBlock {
295295 }
296296
297297 // Unordered list (with nesting support)
298- if trimmed.starts_with ('- ' ) || trimmed.starts_with ('* ' ) || trimmed.starts_with ('+ ' ) {
298+ if left_trimmed.starts_with ('- ' ) || left_trimmed.starts_with ('* ' )
299+ || left_trimmed.starts_with ('+ ' ) {
299300 // Flush any pending runs before list item
300301 if block := flush_runs (mut runs) {
301302 blocks << block
302303 }
303- content , consumed := collect_list_item_content (trimmed [2 ..], lines, i + 1 )
304- mut item_runs := []RichTextRun{}
304+ content , consumed := collect_list_item_content (left_trimmed [2 ..], lines, i + 1 )
305+ mut item_runs := []RichTextRun{cap: 10 }
305306 parse_inline (content, style.text, style, mut item_runs)
306307 blocks << MarkdownBlock{
307308 is_list: true
@@ -316,16 +317,16 @@ fn markdown_to_blocks(source string, style MarkdownStyle) []MarkdownBlock {
316317 }
317318
318319 // Ordered list (with nesting support)
319- if is_ordered_list (trimmed ) {
320+ if is_ordered_list (left_trimmed ) {
320321 // Flush any pending runs before list item
321322 if block := flush_runs (mut runs) {
322323 blocks << block
323324 }
324- dot_pos := trimmed .index ('.' ) or { 0 }
325- num := trimmed [..dot_pos]
326- rest := trimmed [dot_pos + 1 ..].trim_left (' ' )
325+ dot_pos := left_trimmed .index ('.' ) or { 0 }
326+ num := left_trimmed [..dot_pos]
327+ rest := left_trimmed [dot_pos + 1 ..].trim_left (' ' )
327328 content , consumed := collect_list_item_content (rest, lines, i + 1 )
328- mut item_runs := []RichTextRun{}
329+ mut item_runs := []RichTextRun{cap: 10 }
329330 parse_inline (content, style.text, style, mut item_runs)
330331 blocks << MarkdownBlock{
331332 is_list: true
@@ -778,33 +779,38 @@ fn get_indent_level(line string) int {
778779// collect_list_item_content collects the full content of a list item including continuation lines.
779780// Returns the combined content and the number of lines consumed (excluding the first).
780781fn collect_list_item_content (first_content string , lines []string , start_idx int ) (string , int ) {
781- mut content := first_content
782782 mut consumed := 0
783783 mut idx := start_idx
784784
785+ // Check if any continuation lines exist
785786 for idx < lines.len {
786787 next := lines[idx]
787- next_trimmed := next.trim_space ()
788-
789- // Blank line ends the item
790- if next_trimmed == '' {
788+ if next.len == 0 || (next[0 ] != ` ` && next[0 ] != `\t ` ) {
791789 break
792790 }
793- // New block element ends the item
794- if is_block_start (next) {
795- break
796- }
797- // Continuation line - must be indented
798- if next.len > 0 && (next[0 ] == ` ` || next[0 ] == `\t ` ) {
799- content + = ' ' + next_trimmed
800- consumed++
801- idx++
802- } else {
791+ next_trimmed := next.trim_space ()
792+ if next_trimmed == '' || is_block_start (next) {
803793 break
804794 }
795+ consumed++
796+ idx++
797+ }
798+
799+ // Fast path: no continuation lines
800+ if consumed == 0 {
801+ return first_content, 0
805802 }
806803
807- return content, consumed
804+ // Build combined content with buffer
805+ mut buf := []u8 {cap: first_content.len + consumed * 40 }
806+ buf << first_content.bytes ()
807+ idx = start_idx
808+ for _ in 0 .. consumed {
809+ buf << ` `
810+ buf << lines[idx].trim_space ().bytes ()
811+ idx++
812+ }
813+ return buf.bytestr (), consumed
808814}
809815
810816// is_block_start checks if a line starts a new block element.
@@ -884,15 +890,15 @@ fn strip_blockquote_prefix(line string) string {
884890}
885891
886892// is_table_separator checks if a line is a markdown table separator (e.g., |---|---|).
887- fn is_table_separator (line string ) bool {
888- trimmed := line. trim_space ()
889- if trimmed .len < 3 {
893+ // Expects pre-trimmed input.
894+ fn is_table_separator (s string ) bool {
895+ if s .len < 3 {
890896 return false
891897 }
892898 // Must contain at least --- or | and -
893899 mut has_dash := false
894900 mut has_pipe := false
895- for c in trimmed {
901+ for c in s {
896902 if c == `-` {
897903 has_dash = true
898904 } else if c == `|` {
0 commit comments