Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save llimllib/1bbb004e7fc07b96f9d9eeb0b4ea785f to your computer and use it in GitHub Desktop.

Select an option

Save llimllib/1bbb004e7fc07b96f9d9eeb0b4ea785f to your computer and use it in GitHub Desktop.
Claude Code session: fix multiline image wrapping
▐▛███▜▌ Claude Code v2.1.7
▝▜█████▛▘ Opus 4.5 · Claude Pro
▘▘ ▝▝ ~/code/mdriver/multiline-images
❯ We're failing to properly display the image in task.md, I presume because
it's split across two lines
⏺ Let me look at the task.md file and understand the issue.
⏺ Read(task.md)
⎿  Read 5 lines
⏺ Search(pattern: "!\[.*\]", path: "task.md", output_mode: "content")
⎿  Found 0 lines
⏺ I see the issue. The image has a multi-line alt text:
![screenshot of gh pr
status](https://user-images.githubusercontent.com/98482/84171218-327e7a80-aa40-11ea-8cd1-5177fc2d0e72.png)
Let me look at how the parser handles images currently.
⏺ Search(pattern: "image|!\[", path: "src")
⎿  Found 2 files (ctrl+o to expand)
⏺ Search(pattern: "image|Image", path: "src/lib.rs", output_mode: "content")
⎿  Found 69 lines (ctrl+o to expand)
⏺ Read(src/lib.rs)
⎿  Read 60 lines
⏺ Read(src/lib.rs)
⎿  Read 60 lines
⏺ Let me look at how paragraphs/blocks are built to understand the streaming behavior.
⏺ Search(pattern: "BlockBuilder|InParagraph", path: "src/lib.rs", output_mode:
"content", head_limit: 50)
⎿  Found 37 lines (ctrl+o to expand)
⏺ Search(pattern: "fn handle_in_paragraph", path: "src/lib.rs", output_mode:
"content")
⎿  Found 1 line (ctrl+o to expand)
⏺ Read(src/lib.rs)
⎿  Read 50 lines
⏺ Search(pattern: "fn format_paragraph", path: "src/lib.rs", output_mode:
"content")
⎿  Found 1 line (ctrl+o to expand)
⏺ Read(src/lib.rs)
⎿  Read 30 lines
⏺ I see - lines in a paragraph are joined with spaces. Let me trace through the inline formatting to see what happens.
⏺ Read(src/lib.rs)
⎿  Read 30 lines
⏺ Let me run a quick test to see what actually happens when parsing this file.
⏺ Bash(cargo run -- task.md 2>&1 | head -30)
⎿ Compiling mdriver v0.8.0 (/Users/llimllib/code/mdriver/multiline-ima
ges)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.76s
… +8 lines (ctrl+o to expand)
⏺ I see the problem - the image is not being recognized. Let me check what's happening in format_inline when processing this text.
⏺ Read(src/lib.rs)
⎿  Read 50 lines
⏺ Let me check the wrap_text function - it might be introducing newlines.
⏺ Search(pattern: "fn wrap_text", path: "src/lib.rs", output_mode: "content")
⎿  Found 1 line (ctrl+o to expand)
⏺ Read(src/lib.rs)
⎿  Read 80 lines
⏺ Wait - I need to look at the full flow more carefully. Let me check if the order of operations is causing the issue.
⏺ Read(src/lib.rs)
⎿  Read 40 lines
⏺ Read(src/lib.rs)
⎿  Read 50 lines
⏺ Let me debug by checking what format_inline produces for this text.
⏺ Bash(cat > /tmp/test_multiline.rs << 'EOF'
use mdriver::StreamingParser;…)
⎿  (No content)
⏺ Search(pattern: "tests/**/*.toml")
⎿  Found 40 files (ctrl+o to expand)
⏺ Search(pattern: "!\[", path: "tests", output_mode: "content")
⎿  Found 23 lines (ctrl+o to expand)
⏺ Read(tests/fixtures/wrapping/long_url.toml)
⎿  Read 8 lines
⏺ Let me look at how URLs are prevented from being broken during wrapping.
⏺ Search(pattern: "http|url|wrap", path: "src/lib.rs", output_mode: "content",
head_limit: 30)
⎿  Found 30 lines (ctrl+o to expand)
⏺ Read(src/lib.rs)
⎿  Read 50 lines
⏺ Bash(cargo test long_url 2>&1)
⎿   Compiling serde v1.0.228
Compiling winnow v0.7.14
Compiling toml_write v0.1.2
… +43 lines (ctrl+o to expand)
⏺ Bash(cargo test --test conformance -- long 2>&1)
⎿ Finished `test` profile [unoptimized + debuginfo] target(s) in 0.07
s
Running tests/conformance.rs (target/debug/deps/conformance-538c89
… +6 lines (ctrl+o to expand)
⏺ Bash(cargo test --test conformance 2>&1 | head -40)
⎿ Finished `test` profile [unoptimized + debuginfo] target(s) in 0.06
s
Running tests/conformance.rs (target/debug/deps/conformance-538c89
… +11 lines (ctrl+o to expand)
⏺ Let me write a simple test to debug this.
⏺ Bash(cargo test --test unit -- multiline 2>&1)
⎿ Finished `test` profile [unoptimized + debuginfo] target(s) in 0.07
s
Running tests/unit.rs (target/debug/deps/unit-eb0c2ba483b67a7d)
… +5 lines (ctrl+o to expand)
⏺ Read(tests/unit.rs)
⎿  Read 80 lines
⏺ Search(pattern: "format_inline.*!\[", path: "tests/unit.rs", output_mode:
"content")
⎿  Found 1 line (ctrl+o to expand)
⏺ Bash(cat >> tests/unit.rs << 'EOF'…)
⎿ Compiling mdriver v0.8.0 (/Users/llimllib/code/mdriver/multiline-ima
ges)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.37
… +35 lines (ctrl+o to expand)
⏺ Bash(cat >> tests/unit.rs << 'EOF'…)
⎿  Error: Exit code 101
Compiling mdriver v0.8.0 (/Users/llimllib/code/mdriver/multiline-images)
error[E0425]: cannot find function `parser_with_width` in this scope
--> tests/unit.rs:1033:17
|
1033 | let p = parser_with_width(40); // Narrow width to force
wrapping
| ^^^^^^^^^^^^^^^^^ not found in this scope
|
note: function `crate::wrap_text_tests::parser_with_width` exists but is
inaccessible
--> tests/unit.rs:345:5
… +15 lines (ctrl+o to see all)
⏺ Read(tests/unit.rs)
⎿  Read 20 lines
⏺ Read(tests/unit.rs)
⎿  Read 22 lines
⏺ Update(tests/unit.rs)
⎿  Added 5 lines, removed 1 line
1027 #[cfg(test)]
1028 mod wrapping_image_debug {
1029 use super::*;
1030 -
1030 +
1031 + fn parser_with_width(width: usize) -> StreamingParser {
1032 + StreamingParser::with_width("base16-ocean.dark",
+ImageProtocol::None, width)
1033 + }
1034 +
1035 #[test]
1036 fn test_wrap_image_markdown() {
1037 let p = parser_with_width(40); // Narrow width to
force wrapping
⏺ Bash(cargo test wrapping_image_debug -- --nocapture 2>&1)
⎿ Compiling mdriver v0.8.0 (/Users/llimllib/code/mdriver/multiline-ima
ges)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.35
… +36 lines (ctrl+o to expand)
⏺ Now I understand the issue. The wrap_text function is tokenizing the image markdown on whitespace and wrapping it, breaking the ![alt](url) syntax across lines. The image markdown needs to be treated as a single unwrappable unit.
Let me fix wrap_text to recognize image markdown patterns and keep them as single tokens.
⏺ Read(src/lib.rs)
⎿  Read 80 lines
⏺ Update(src/lib.rs)
⎿  Added 41 lines
1556 current_token.push(chars[i]);
1557 i += 1;
1558 }
1559 + } else if chars[i] == '!' && i + 1 <
+chars.len() && chars[i + 1] == '[' {
1560 + // Image markdown: ![alt](url) - keep as
+single token
1561 + // First, flush any existing token
1562 + if !current_token.is_empty() {
1563 + tokens.push(current_token);
1564 + current_token = String::new();
1565 + }
1566 + // Start building the image token
1567 + current_token.push(chars[i]); // '!'
1568 + current_token.push(chars[i + 1]); // '['
1569 + i += 2;
1570 + // Find closing ]
1571 + let mut bracket_depth = 1;
1572 + while i < chars.len() && bracket_depth > 0
+{
1573 + if chars[i] == '[' {
1574 + bracket_depth += 1;
1575 + } else if chars[i] == ']' {
1576 + bracket_depth -= 1;
1577 + }
1578 + current_token.push(chars[i]);
1579 + i += 1;
1580 + }
1581 + // Check for ( following ]
1582 + if i < chars.len() && chars[i] == '(' {
1583 + current_token.push(chars[i]);
1584 + i += 1;
1585 + // Find closing )
1586 + let mut paren_depth = 1;
1587 + while i < chars.len() && paren_depth >
+0 {
1588 + if chars[i] == '(' {
1589 + paren_depth += 1;
1590 + } else if chars[i] == ')' {
1591 + paren_depth -= 1;
1592 + }
1593 + current_token.push(chars[i]);
1594 + i += 1;
1595 + }
1596 + }
1597 + // The image token is complete, push it
1598 + tokens.push(current_token);
1599 + current_token = String::new();
1600 } else if chars[i].is_whitespace() {
1601 if !current_token.is_empty() {
1602 tokens.push(current_token);
⎿  You've hit your limit · resets 6pm (America/New_York)
/upgrade to increase your usage limit.
✻ Cooked for 6m 57s
❯ continue
⏺ Bash(cargo test wrapping_image_debug -- --nocapture 2>&1)
⎿ Compiling mdriver v0.8.0 (/Users/llimllib/code/mdriver/multiline-ima
ges)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.97
… +35 lines (ctrl+o to expand)
⏺ The image markdown is now kept intact. Let me test on the actual file.
⏺ Bash(cargo run -- task.md 2>&1)
⎿ Compiling mdriver v0.8.0 (/Users/llimllib/code/mdriver/multiline-ima
ges)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.92s
… +7 lines (ctrl+o to expand)
⏺ The image markdown is now on a single line. Let me run all tests to ensure nothing is broken.
⏺ Bash(cargo test 2>&1)
⎿ Finished `test` profile [unoptimized + debuginfo] target(s) in 0.07
s
Running unittests src/lib.rs (target/debug/deps/mdriver-88c5f32734
… +146 lines (ctrl+o to expand)
⏺ All tests pass. Let me run clippy and fmt as required by the project.
⏺ Bash(cargo fmt && cargo clippy --all-targets --all-features -- -D warnings
2>&1)
⎿   Checking stable_deref_trait v1.2.1
Checking cfg-if v1.0.4
Checking memchr v2.7.6
… +138 lines (ctrl+o to expand)
⏺ All checks pass. Let me clean up the debug tests to make them proper tests for the multiline image behavior.
⏺ Read(tests/unit.rs)
⎿  Read 47 lines
⏺ Update(tests/unit.rs)
⎿  Added 19 lines, removed 11 lines
1003 }
1004
1005 #[cfg(test)]
1006 - mod multiline_image_debug {
1006 + mod markdown_image_tests {
1007 use super::*;
1008
1009 #[test]
1010 - fn test_multiline_image_alt_text() {
1010 + fn test_image_with_spaces_in_alt() {
1011 let p = parser();
1012 - // When lines are joined with space, this is what
-we get
1012 + // Image with spaces in alt text should be parsed
+correctly
1013 let result =
1014 p.format_inline("![screenshot of gh pr
status](https://example.com/image.png)");
1015 - println!("Result: {:?}", result);
1015 assert_eq!(
1016 result,
1017 "![screenshot of gh pr
status](https://example.com/image.png)"
...
1023 fn test_simple_image() {
1024 let p = parser();
1025 let result =
p.format_inline("![alt](https://example.com/image.png)");
1026 - println!("Result: {:?}", result);
1026 assert_eq!(result,
"![alt](https://example.com/image.png)");
1027 }
1028 }
1029
1030 #[cfg(test)]
1031 - mod wrapping_image_debug {
1031 + mod wrap_image_tests {
1032 use super::*;
1033
1034 fn parser_with_width(width: usize) -> StreamingParser {
...
1037 }
1038
1039 #[test]
1040 - fn test_wrap_image_markdown() {
1041 - let p = parser_with_width(40); // Narrow width to
-force wrapping
1042 - // Image markdown
-after format_inline
1040 + fn test_image_markdown_not_broken_by_wrapping() {
1041 + // Image markdown should be kept as a single unit
+and not broken across lines
1042 + let p = parser_with_width(40);
1043 let image_md = "![screenshot of gh pr
status](https://user-images.githubusercontent.com/98482/8417
1218-327e7a80-aa40-11ea-8cd1-5177fc2d0e72.png)";
1044 let result = p.wrap_text(image_md, "", "");
1045 - println!("Wrapped result:\n{}", result);
1046 - // With width 40, this will definitely need to wrap
1045 + // The entire image markdown should be on one line
+(even if it exceeds width)
1046 + assert!(!result.contains('\n') || result.trim() ==
+image_md);
1047 }
1048 +
1049 + #[test]
1050 + fn test_text_with_image_wraps_correctly() {
1051 + // Text before/after image can wrap, but image
+stays intact
1052 + let p = parser_with_width(40);
1053 + let text = "Here is some text
+![alt](https://example.com/img.png) and more text";
1054 + let result = p.wrap_text(text, "", "");
1055 + // Image should be intact in the result
1056 + assert!(result.contains("![alt](https://example.com
+/img.png)"));
1057 + }
1058 }
⏺ Bash(cargo fmt && cargo test 2>&1 | tail -30)
⎿  test reference_links::test_case_insensitive_label ... ok
test reference_links::test_definition_with_title ... ok
test reference_links::test_first_definition_wins ... ok
… +29 lines (ctrl+o to expand)
⏺ Bash(cargo clippy --all-targets --all-features -- -D warnings 2>&1 | tail -5)
⎿ Checking mdriver v0.8.0 (/Users/llimllib/code/mdriver/multiline-ima
ges)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.20s
⏺ The fix is complete. Here's a summary:
Problem: Images with multiline alt text like:
![screenshot of gh pr
status](https://example.com/image.png)
were being displayed with the markdown broken across lines because wrap_text was tokenizing on whitespace and wrapping the image markdown.
Solution: Modified wrap_text in src/lib.rs:1559-1599 to recognize image markdown patterns (![alt](url)) and keep them as single unwrappable tokens. When tokenizing text, if we encounter ![, we now:
1. Find the closing ] (handling nested brackets)
2. Look for the following (
3. Find the closing ) (handling nested parens)
4. Keep the entire ![...](...) as a single token
This ensures image markdown is never broken by line wrapping, similar to how URLs should not be broken.
Tests added in tests/unit.rs:
- test_image_with_spaces_in_alt - verifies images with spaces in alt text parse correctly
- test_simple_image - basic image parsing
- test_image_markdown_not_broken_by_wrapping - ensures image markdown stays on one line
- test_text_with_image_wraps_correctly - text around images can wrap, but image stays intact
✻ Baked for 1m 17s
❯ commit the changes
⏺ Bash(git status)
⎿  On branch multiline-images
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
… +9 lines (ctrl+o to expand)
⏺ Bash(git diff)
⎿  diff --git i/src/lib.rs w/src/lib.rs
index ece96cc..503f8f7 100644
--- i/src/lib.rs
… +121 lines (ctrl+o to expand)
⏺ Bash(git log --oneline -5)
⎿ c8e1499 fix: use configured width for horizontal rules instead of termi
nal detection (#15)
a15e7ae feat: strip HTML comments from markdown output (#14)
… +5 lines (ctrl+o to expand)
⏺ Bash(git add src/lib.rs tests/unit.rs && git commit -m "fix: prevent image
markdown from being broken by line wrapping…)
⎿ [multiline-images 690016b] fix: prevent image markdown from being broke
n by line wrapping
2 files changed, 94 insertions(+)
⏺ Bash(git status)
⎿  On branch multiline-images
Untracked files:
(use "git add <file>..." to include in what will be committed)
… +4 lines (ctrl+o to expand)
⏺ Done. Committed to branch multiline-images:
690016b fix: prevent image markdown from being broken by line wrapping
The task.md test file was left untracked since it's not part of the fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment