Skip to content

Add footnote wrap handling#73

Merged
leynos merged 5 commits intomainfrom
codex/add-test-for-multi-line-footnote-indentation
Jul 16, 2025
Merged

Add footnote wrap handling#73
leynos merged 5 commits intomainfrom
codex/add-test-for-multi-line-footnote-indentation

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Jul 16, 2025

Summary

  • support wrapping footnote definitions with proper indentation
  • verify multi-line footnotes wrap correctly

Testing

  • make fmt
  • make lint
  • make test

https://chatgpt.com/codex/tasks/task_e_6876cdda278c83228287b9522ef84593

Summary by Sourcery

Add support for wrapping footnote definitions with proper indentation in text wrapping functionality

Enhancements:

  • Introduce FOOTNOTE_RE to recognize footnote definition lines during wrapping
  • Extend wrap_text to flush paragraphs and wrap footnote content with consistent indentation

Tests:

  • Add integration test to verify multiline footnote definitions wrap and indent correctly

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Jul 16, 2025

Reviewer's Guide

This PR enhances wrap_text to support wrapping of footnote definitions with proper indentation by introducing a FOOTNOTE_RE regex and corresponding handling logic, and adds an integration test to ensure multi-line footnotes are wrapped correctly.

Class diagram for updated wrap_text footnote handling

classDiagram
    class wrap_text {
        +wrap_text(lines: &[String], width: usize) Vec<String>
    }
    class FOOTNOTE_RE {
        +Regex: LazyLock<Regex>
    }
    wrap_text ..> FOOTNOTE_RE : uses
Loading

Flow diagram for footnote wrapping logic in wrap_text

flowchart TD
    A[Start wrap_text] --> B{Line matches FOOTNOTE_RE?}
    B -- No --> C[Other wrapping logic]
    B -- Yes --> D[Flush current paragraph]
    D --> E[Clear buffer and indent]
    E --> F[Extract prefix and rest]
    F --> G[Wrap rest with wrap_preserving_code]
    G --> H{First line?}
    H -- Yes --> I[Output prefix + line]
    H -- No --> J[Output spaces + line]
    I & J --> K[Continue]
    C --> K
    K --> L[Next line or End]
Loading

File-Level Changes

Change Details Files
Support footnote definition wrapping in wrap_text
  • Add static FOOTNOTE_RE regex to detect footnote definitions
  • Flush existing paragraph buffer when encountering a footnote line
  • Extract prefix and content, compute indentation spaces
  • Use wrap_preserving_code to wrap content and format lines with correct indent
src/lib.rs
Add integration test for multi-line footnote wrapping
  • Implement test_wrap_footnote_multiline using common::assert_wrapped_list_item
  • Provide a long footnote definition to verify wrapping behavior
tests/integration.rs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jul 16, 2025

Summary by CodeRabbit

  • New Features
    • Improved handling and wrapping of Markdown footnote definitions, ensuring correct indentation and line wrapping.
  • Tests
    • Added tests to verify proper wrapping of footnote definitions, including cases with inline code and varying indentation.
  • Documentation
    • Updated contributor guidelines with improved formatting and a new recommendation for combining long string literals in Rust.

Summary by CodeRabbit

  • New Features

    • Added support for correctly wrapping markdown footnote definitions, preserving their formatting and indentation.
  • Tests

    • Introduced new integration tests to verify correct line wrapping for multiline footnote definitions, including cases with inline code.
  • Documentation

    • Updated Rust contributor guidance with a new best practice for string literal concatenation.

Walkthrough

Extend the text wrapping logic to recognise and handle markdown footnote definitions. Add a static regular expression to detect footnote lines, update the wrap_text function to wrap such lines while preserving their prefix indentation, and introduce integration tests to verify correct multi-line footnote wrapping including inline code handling.

Changes

File(s) Change Summary
src/lib.rs Add static regex FOOTNOTE_RE for footnote detection; introduce append_wrapped_with_prefix; update wrap_text to wrap footnote lines preserving prefix indentation; refactor bullet list wrapping to use new helper.
tests/integration.rs Add tests test_wrap_footnote_multiline and test_wrap_footnote_with_inline_code to verify multi-line wrapping for footnote definitions, including inline code handling.
AGENTS.md Update Rust contributor guidance with a new best practice for string literal concatenation using concat!().

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant wrap_text
    participant FOOTNOTE_RE

    Caller->>wrap_text: Pass lines, width
    loop For each line
        wrap_text->>FOOTNOTE_RE: Check if line matches footnote pattern
        alt Footnote match
            wrap_text->>wrap_text: Flush buffer, clear indentation
            wrap_text->>wrap_text: Wrap content after prefix with indentation
            wrap_text->>Caller: Output wrapped footnote lines
        else Not a footnote
            wrap_text->>wrap_text: Continue standard wrapping logic (including bullet lists)
        end
    end
    wrap_text->>Caller: Return wrapped lines
Loading

Possibly related PRs

Poem

Footnotes now wrap with style and grace,
Their prefixes held in the right place.
Regex at the ready, indentation in tow,
Multiline notes in tidy rows.
Markdown’s quirks, now less opaque—
A footnote fix for clarity’s sake!
📝✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bad9797 and 35c03a5.

📒 Files selected for processing (1)
  • src/lib.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • AGENTS.md

⚙️ CodeRabbit Configuration File

🔇 Additional comments (4)
src/lib.rs (4)

229-230: LGTM! The footnote regex correctly implements the full label syntax.

The updated pattern using [^]]+ properly allows any character except the closing bracket in footnote labels, making it compliant with the Markdown specification.


406-424: Excellent implementation of prefix-preserving wrapping with proper Unicode width handling.

The function correctly addresses previous feedback by using UnicodeWidthStr for display width calculations instead of byte length, ensuring proper alignment for prefixes containing tabs or multi-byte characters.


511-514: Good refactoring to use the shared helper function.

The bullet list handling now uses the new append_wrapped_with_prefix helper, eliminating code duplication. The decision to not trim the rest content preserves intentional whitespace, which addresses previous concerns about removing meaningful spaces.


516-526: Well-implemented footnote handling with consistent behaviour.

The footnote wrapping logic correctly mirrors the bullet list handling approach, using the shared helper function and maintaining consistent whitespace preservation. The prefix construction properly combines indentation and label parts for accurate wrapped line alignment.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/add-test-for-multi-line-footnote-indentation

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @leynos - I've reviewed your changes and found some issues that need to be addressed.

  • Consider refactoring the footnote wrapping logic to reuse your existing list-item wrapping function instead of duplicating almost identical code.
  • Ensure your FOOTNOTE_RE handles optional leading indentation and more complex identifiers (e.g. underscores, hyphens) by extending the regex or normalizing the prefix capture.
  • Add a test for footnote definitions that include inline code or other Markdown spans to verify wrap_preserving_code is applied correctly.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider refactoring the footnote wrapping logic to reuse your existing list-item wrapping function instead of duplicating almost identical code.
- Ensure your FOOTNOTE_RE handles optional leading indentation and more complex identifiers (e.g. underscores, hyphens) by extending the regex or normalizing the prefix capture.
- Add a test for footnote definitions that include inline code or other Markdown spans to verify wrap_preserving_code is applied correctly.

## Individual Comments

### Comment 1
<location> `src/lib.rs:506` </location>
<code_context>
             continue;
         }

+        if let Some(cap) = FOOTNOTE_RE.captures(line) {
+            flush_paragraph(&mut out, &buf, &indent, width);
+            buf.clear();
+            indent.clear();
+            let prefix = cap.get(1).unwrap().as_str();
+            let rest = cap.get(2).unwrap().as_str().trim();
+            let spaces = " ".repeat(prefix.len());
+            for (i, l) in wrap_preserving_code(rest, width - prefix.len())
+                .iter()
+                .enumerate()
+            {
+                if i == 0 {
+                    out.push(format!("{prefix}{l}"));
+                } else {
+                    out.push(format!("{spaces}{l}"));
+                }
+            }
+            continue;
+        }
+
</code_context>

<issue_to_address>
The logic for handling footnote lines may not preserve leading whitespace for indented footnotes.

Using only the prefix length to generate spaces may misalign wrapped lines if the original indentation used mixed whitespace. Preserving the exact original indentation could improve alignment.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        if let Some(cap) = FOOTNOTE_RE.captures(line) {
            flush_paragraph(&mut out, &buf, &indent, width);
            buf.clear();
            indent.clear();
            let prefix = cap.get(1).unwrap().as_str();
            let rest = cap.get(2).unwrap().as_str().trim();
            let spaces = " ".repeat(prefix.len());
            for (i, l) in wrap_preserving_code(rest, width - prefix.len())
                .iter()
                .enumerate()
            {
                if i == 0 {
                    out.push(format!("{prefix}{l}"));
                } else {
                    out.push(format!("{spaces}{l}"));
                }
            }
            continue;
        }
=======
        if let Some(cap) = FOOTNOTE_RE.captures(line) {
            flush_paragraph(&mut out, &buf, &indent, width);
            buf.clear();
            indent.clear();
            let prefix = cap.get(1).unwrap().as_str();
            let rest = cap.get(2).unwrap().as_str().trim();

            // Extract the exact leading whitespace from the prefix
            let orig_indent = prefix
                .chars()
                .take_while(|c| c.is_whitespace())
                .collect::<String>();
            // For wrapped lines, use the same indentation as the prefix
            let wrapped_indent = format!(
                "{}{}",
                orig_indent,
                " ".repeat(prefix.trim_start().len())
            );

            for (i, l) in wrap_preserving_code(rest, width - prefix.len())
                .iter()
                .enumerate()
            {
                if i == 0 {
                    out.push(format!("{prefix}{l}"));
                } else {
                    out.push(format!("{wrapped_indent}{l}"));
                }
            }
            continue;
        }
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `src/lib.rs:510` </location>
<code_context>
+            flush_paragraph(&mut out, &buf, &indent, width);
+            buf.clear();
+            indent.clear();
+            let prefix = cap.get(1).unwrap().as_str();
+            let rest = cap.get(2).unwrap().as_str().trim();
+            let spaces = " ".repeat(prefix.len());
+            for (i, l) in wrap_preserving_code(rest, width - prefix.len())
</code_context>

<issue_to_address>
Trimming the footnote content may remove intentional leading/trailing spaces.

Using .trim() here may unintentionally remove meaningful whitespace from footnote content. Please verify if this is required for your use case.
</issue_to_address>

### Comment 3
<location> `src/lib.rs:513` </location>
<code_context>
+            let prefix = cap.get(1).unwrap().as_str();
+            let rest = cap.get(2).unwrap().as_str().trim();
+            let spaces = " ".repeat(prefix.len());
+            for (i, l) in wrap_preserving_code(rest, width - prefix.len())
+                .iter()
+                .enumerate()
+            {
+                if i == 0 {
+                    out.push(format!("{prefix}{l}"));
+                } else {
+                    out.push(format!("{spaces}{l}"));
+                }
+            }
+            continue;
</code_context>

<issue_to_address>
Subtracting prefix length from width may cause issues if prefix contains non-printable characters.

Since .len() returns the byte length, not the display width, this can misalign output for prefixes with tabs or multi-byte characters. Use a display width calculation to ensure correct wrapping.

Suggested implementation:

```rust
            use unicode_width::UnicodeWidthStr;
            let prefix = cap.get(1).unwrap().as_str();
            let rest = cap.get(2).unwrap().as_str().trim();
            let prefix_width = UnicodeWidthStr::width(prefix);
            let spaces = " ".repeat(prefix_width);

```

```rust
            for (i, l) in wrap_preserving_code(rest, width - prefix_width)

```

Make sure the `unicode-width` crate is included in your `Cargo.toml` dependencies:

```toml
[dependencies]
unicode-width = "0.1"
```

If the `use unicode_width::UnicodeWidthStr;` import is already present at the top of the file, you can omit it from the inner scope.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/lib.rs
Comment thread src/lib.rs Outdated
Comment thread src/lib.rs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41797b7 and 60f2876.

📒 Files selected for processing (2)
  • src/lib.rs (2 hunks)
  • tests/integration.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • AGENTS.md

⚙️ CodeRabbit Configuration File

🧬 Code Graph Analysis (1)
tests/integration.rs (2)
src/lib.rs (1)
  • process_stream (746-746)
tests/common/mod.rs (1)
  • assert_wrapped_list_item (17-53)
🔇 Additional comments (2)
src/lib.rs (1)

229-230: LGTM: Regex pattern correctly matches footnote definitions.

The pattern properly captures footnote markers like [^note]: content with appropriate grouping for prefix and content extraction.

tests/integration.rs (1)

719-728: LGTM: Test properly validates footnote wrapping functionality.

The test correctly verifies that long footnote definitions wrap with proper indentation, following the same pattern as list item tests.

Comment thread src/lib.rs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/lib.rs (1)

516-525: Ensure footnote content trimming is intentional.

The footnote content is trimmed with .trim() on line 524, which could remove meaningful whitespace from footnote definitions. Past review comments raised this concern.

Verify that trimming footnote content doesn't affect legitimate use cases where leading or trailing spaces are significant.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c25dcc and 1023704.

📒 Files selected for processing (2)
  • src/lib.rs (3 hunks)
  • tests/integration.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • AGENTS.md

⚙️ CodeRabbit Configuration File

🔇 Additional comments (4)
src/lib.rs (2)

406-424: Addresses past review comments on Unicode width handling.

The helper function correctly uses unicode_width::UnicodeWidthStr to calculate display widths, addressing the previous concern about byte length vs display width calculations. The implementation properly handles mixed whitespace in prefixes.


512-514: Addresses code duplication through helper function.

The refactoring to use append_wrapped_with_prefix eliminates the duplication previously flagged between bullet and footnote handling logic.

tests/integration.rs (2)

732-743: Test covers inline code preservation in footnotes.

The test properly verifies that inline code spans are preserved during footnote wrapping, which is crucial for maintaining markdown structure.


719-730: Drop incorrect indentation level assumption

The expected argument denotes the number of wrapped lines (2), not two spaces of indentation. The prefix [^note]: is 9 characters long, so wrapped lines correctly indent by 9 spaces. No changes required.

Likely an incorrect or invalid review comment.

Comment thread src/lib.rs Outdated
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jul 16, 2025

@sourcery-ai resolve

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1023704 and bad9797.

📒 Files selected for processing (1)
  • src/lib.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • AGENTS.md

⚙️ CodeRabbit Configuration File

🔇 Additional comments (2)
src/lib.rs (2)

229-230: LGTM! Regex pattern correctly updated.

The footnote regex pattern now properly allows any character except the closing bracket in footnote labels, addressing the previous concern about being too restrictive compared to the Markdown specification.


406-424: Excellent helper function implementation.

The helper function properly addresses previous concerns about Unicode width handling and code duplication. The implementation correctly:

  • Uses UnicodeWidthStr::width() for accurate display width calculations
  • Extracts leading whitespace from the prefix for proper indentation
  • Creates consistent indentation for wrapped lines
  • Provides reusable logic for both bullet lists and footnotes

Comment thread src/lib.rs
@leynos leynos merged commit 02fa99a into main Jul 16, 2025
2 checks passed
@leynos leynos deleted the codex/add-test-for-multi-line-footnote-indentation branch July 16, 2025 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant