Coverage Report

Created: 2025-11-16 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibMarkdown/ContainerBlock.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2021, Peter Elliott <pelliott@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <AK/Forward.h>
8
#include <LibMarkdown/BlockQuote.h>
9
#include <LibMarkdown/CodeBlock.h>
10
#include <LibMarkdown/ContainerBlock.h>
11
#include <LibMarkdown/Heading.h>
12
#include <LibMarkdown/HorizontalRule.h>
13
#include <LibMarkdown/List.h>
14
#include <LibMarkdown/Paragraph.h>
15
#include <LibMarkdown/Table.h>
16
#include <LibMarkdown/Visitor.h>
17
18
namespace Markdown {
19
20
ByteString ContainerBlock::render_to_html(bool tight) const
21
0
{
22
0
    StringBuilder builder;
23
24
0
    for (size_t i = 0; i + 1 < m_blocks.size(); ++i) {
25
0
        auto s = m_blocks[i]->render_to_html(tight);
26
0
        builder.append(s);
27
0
    }
28
29
    // I don't like this edge case.
30
0
    if (m_blocks.size() != 0) {
31
0
        auto& block = m_blocks[m_blocks.size() - 1];
32
0
        auto s = block->render_to_html(tight);
33
0
        if (tight && dynamic_cast<Paragraph const*>(block.ptr())) {
34
0
            builder.append(s.substring_view(0, s.length() - 1));
35
0
        } else {
36
0
            builder.append(s);
37
0
        }
38
0
    }
39
40
0
    return builder.to_byte_string();
41
0
}
42
43
Vector<ByteString> ContainerBlock::render_lines_for_terminal(size_t view_width) const
44
0
{
45
0
    Vector<ByteString> lines;
46
47
0
    for (auto& block : m_blocks) {
48
0
        for (auto& line : block->render_lines_for_terminal(view_width))
49
0
            lines.append(move(line));
50
0
    }
51
52
0
    return lines;
53
0
}
54
55
RecursionDecision ContainerBlock::walk(Visitor& visitor) const
56
0
{
57
0
    RecursionDecision rd = visitor.visit(*this);
58
0
    if (rd != RecursionDecision::Recurse)
59
0
        return rd;
60
61
0
    for (auto const& block : m_blocks) {
62
0
        rd = block->walk(visitor);
63
0
        if (rd == RecursionDecision::Break)
64
0
            return rd;
65
0
    }
66
67
0
    return RecursionDecision::Continue;
68
0
}
69
70
template<class CodeBlock>
71
static bool try_parse_block(LineIterator& lines, Vector<NonnullOwnPtr<Block>>& blocks, Heading* current_section)
72
16.9M
{
73
16.9M
    OwnPtr<CodeBlock> block = CodeBlock::parse(lines, current_section);
74
16.9M
    if (!block)
75
13.8M
        return false;
76
3.05M
    blocks.append(block.release_nonnull());
77
3.05M
    return true;
78
16.9M
}
79
80
template<typename BlockType>
81
static bool try_parse_block(LineIterator& lines, Vector<NonnullOwnPtr<Block>>& blocks)
82
96.6M
{
83
96.6M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
96.6M
    if (!block)
85
93.2M
        return false;
86
3.42M
    blocks.append(block.release_nonnull());
87
3.42M
    return true;
88
96.6M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::Heading>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
17.6M
{
83
17.6M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
17.6M
    if (!block)
85
17.1M
        return false;
86
496k
    blocks.append(block.release_nonnull());
87
496k
    return true;
88
17.6M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::Table>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
17.1M
{
83
17.1M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
17.1M
    if (!block)
85
17.1M
        return false;
86
20.1k
    blocks.append(block.release_nonnull());
87
20.1k
    return true;
88
17.1M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::HorizontalRule>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
17.1M
{
83
17.1M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
17.1M
    if (!block)
85
17.0M
        return false;
86
73.9k
    blocks.append(block.release_nonnull());
87
73.9k
    return true;
88
17.1M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::List>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
17.0M
{
83
17.0M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
17.0M
    if (!block)
85
16.9M
        return false;
86
132k
    blocks.append(block.release_nonnull());
87
132k
    return true;
88
17.0M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::CommentBlock>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
13.8M
{
83
13.8M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
13.8M
    if (!block)
85
13.8M
        return false;
86
19.4k
    blocks.append(block.release_nonnull());
87
19.4k
    return true;
88
13.8M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::BlockQuote>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
13.8M
{
83
13.8M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
13.8M
    if (!block)
85
11.1M
        return false;
86
2.67M
    blocks.append(block.release_nonnull());
87
2.67M
    return true;
88
13.8M
}
89
90
OwnPtr<ContainerBlock> ContainerBlock::parse(LineIterator& lines)
91
3.61M
{
92
3.61M
    Vector<NonnullOwnPtr<Block>> blocks;
93
94
3.61M
    StringBuilder paragraph_text;
95
3.61M
    Heading* current_section = nullptr;
96
97
21.8M
    auto flush_paragraph = [&] {
98
21.8M
        if (paragraph_text.is_empty())
99
19.2M
            return;
100
2.62M
        auto paragraph = make<Paragraph>(Text::parse(paragraph_text.to_byte_string()));
101
2.62M
        blocks.append(move(paragraph));
102
2.62M
        paragraph_text.clear();
103
2.62M
    };
104
105
3.61M
    bool has_blank_lines = false;
106
3.61M
    bool has_trailing_blank_lines = false;
107
108
36.9M
    while (true) {
109
36.9M
        if (lines.is_end())
110
3.61M
            break;
111
112
33.3M
        if ((*lines).is_whitespace()) {
113
15.6M
            has_trailing_blank_lines = true;
114
15.6M
            ++lines;
115
116
15.6M
            flush_paragraph();
117
15.6M
            continue;
118
17.6M
        } else {
119
17.6M
            has_blank_lines = has_blank_lines || has_trailing_blank_lines;
120
17.6M
        }
121
122
17.6M
        bool heading = false;
123
17.6M
        if ((heading = try_parse_block<Heading>(lines, blocks)))
124
496k
            current_section = dynamic_cast<Heading*>(blocks.last().ptr());
125
126
17.6M
        bool any = heading
127
17.1M
            || try_parse_block<Table>(lines, blocks)
128
17.1M
            || try_parse_block<HorizontalRule>(lines, blocks)
129
17.0M
            || try_parse_block<List>(lines, blocks)
130
            // CodeBlock needs to know the current section's name for proper indentation
131
16.9M
            || try_parse_block<CodeBlock>(lines, blocks, current_section)
132
13.8M
            || try_parse_block<CommentBlock>(lines, blocks)
133
13.8M
            || try_parse_block<BlockQuote>(lines, blocks);
134
135
17.6M
        if (any) {
136
6.47M
            if (!paragraph_text.is_empty()) {
137
2.54M
                auto last_block = blocks.take_last();
138
2.54M
                flush_paragraph();
139
2.54M
                blocks.append(move(last_block));
140
2.54M
            }
141
6.47M
            continue;
142
6.47M
        }
143
144
11.1M
        if (!paragraph_text.is_empty())
145
8.54M
            paragraph_text.append('\n');
146
11.1M
        paragraph_text.append(*lines++);
147
11.1M
    }
148
149
3.61M
    flush_paragraph();
150
151
3.61M
    return make<ContainerBlock>(move(blocks), has_blank_lines, has_trailing_blank_lines);
152
3.61M
}
153
154
}