Coverage Report

Created: 2026-10-28 07:05

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
21.8M
{
73
21.8M
    OwnPtr<CodeBlock> block = CodeBlock::parse(lines, current_section);
74
21.8M
    if (!block)
75
17.7M
        return false;
76
4.04M
    blocks.append(block.release_nonnull());
77
4.04M
    return true;
78
21.8M
}
79
80
template<typename BlockType>
81
static bool try_parse_block(LineIterator& lines, Vector<NonnullOwnPtr<Block>>& blocks)
82
123M
{
83
123M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
123M
    if (!block)
85
118M
        return false;
86
5.66M
    blocks.append(block.release_nonnull());
87
5.66M
    return true;
88
123M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::Heading>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
22.3M
{
83
22.3M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
22.3M
    if (!block)
85
21.9M
        return false;
86
408k
    blocks.append(block.release_nonnull());
87
408k
    return true;
88
22.3M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::Table>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
21.9M
{
83
21.9M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
21.9M
    if (!block)
85
21.9M
        return false;
86
6.54k
    blocks.append(block.release_nonnull());
87
6.54k
    return true;
88
21.9M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::HorizontalRule>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
21.9M
{
83
21.9M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
21.9M
    if (!block)
85
21.9M
        return false;
86
61.5k
    blocks.append(block.release_nonnull());
87
61.5k
    return true;
88
21.9M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::List>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
21.9M
{
83
21.9M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
21.9M
    if (!block)
85
21.8M
        return false;
86
63.1k
    blocks.append(block.release_nonnull());
87
63.1k
    return true;
88
21.9M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::CommentBlock>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
17.7M
{
83
17.7M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
17.7M
    if (!block)
85
17.7M
        return false;
86
12.3k
    blocks.append(block.release_nonnull());
87
12.3k
    return true;
88
17.7M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::BlockQuote>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
17.7M
{
83
17.7M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
17.7M
    if (!block)
85
12.6M
        return false;
86
5.11M
    blocks.append(block.release_nonnull());
87
5.11M
    return true;
88
17.7M
}
89
90
OwnPtr<ContainerBlock> ContainerBlock::parse(LineIterator& lines)
91
6.15M
{
92
6.15M
    Vector<NonnullOwnPtr<Block>> blocks;
93
94
6.15M
    StringBuilder paragraph_text;
95
6.15M
    Heading* current_section = nullptr;
96
97
28.9M
    auto flush_paragraph = [&] {
98
28.9M
        if (paragraph_text.is_empty())
99
23.9M
            return;
100
4.91M
        auto paragraph = make<Paragraph>(Text::parse(paragraph_text.to_byte_string()));
101
4.91M
        blocks.append(move(paragraph));
102
4.91M
        paragraph_text.clear();
103
4.91M
    };
104
105
6.15M
    bool has_blank_lines = false;
106
6.15M
    bool has_trailing_blank_lines = false;
107
108
46.4M
    while (true) {
109
46.4M
        if (lines.is_end())
110
6.15M
            break;
111
112
40.2M
        if ((*lines).is_whitespace()) {
113
17.9M
            has_trailing_blank_lines = true;
114
17.9M
            ++lines;
115
116
17.9M
            flush_paragraph();
117
17.9M
            continue;
118
22.3M
        } else {
119
22.3M
            has_blank_lines = has_blank_lines || has_trailing_blank_lines;
120
22.3M
        }
121
122
22.3M
        bool heading = false;
123
22.3M
        if ((heading = try_parse_block<Heading>(lines, blocks)))
124
408k
            current_section = dynamic_cast<Heading*>(blocks.last().ptr());
125
126
22.3M
        bool any = heading
127
21.9M
            || try_parse_block<Table>(lines, blocks)
128
21.9M
            || try_parse_block<HorizontalRule>(lines, blocks)
129
21.9M
            || try_parse_block<List>(lines, blocks)
130
            // CodeBlock needs to know the current section's name for proper indentation
131
21.8M
            || try_parse_block<CodeBlock>(lines, blocks, current_section)
132
17.7M
            || try_parse_block<CommentBlock>(lines, blocks)
133
17.7M
            || try_parse_block<BlockQuote>(lines, blocks);
134
135
22.3M
        if (any) {
136
9.70M
            if (!paragraph_text.is_empty()) {
137
4.83M
                auto last_block = blocks.take_last();
138
4.83M
                flush_paragraph();
139
4.83M
                blocks.append(move(last_block));
140
4.83M
            }
141
9.70M
            continue;
142
9.70M
        }
143
144
12.6M
        if (!paragraph_text.is_empty())
145
7.76M
            paragraph_text.append('\n');
146
12.6M
        paragraph_text.append(*lines++);
147
12.6M
    }
148
149
6.15M
    flush_paragraph();
150
151
6.15M
    return make<ContainerBlock>(move(blocks), has_blank_lines, has_trailing_blank_lines);
152
6.15M
}
153
154
}