Coverage Report

Created: 2025-09-05 06:52

/src/serenity/Userland/Libraries/LibMarkdown/ContainerBlock.cpp
Line
Count
Source (jump to first uncovered line)
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
18.9M
{
73
18.9M
    OwnPtr<CodeBlock> block = CodeBlock::parse(lines, current_section);
74
18.9M
    if (!block)
75
15.8M
        return false;
76
3.13M
    blocks.append(block.release_nonnull());
77
3.13M
    return true;
78
18.9M
}
79
80
template<typename BlockType>
81
static bool try_parse_block(LineIterator& lines, Vector<NonnullOwnPtr<Block>>& blocks)
82
108M
{
83
108M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
108M
    if (!block)
85
105M
        return false;
86
3.51M
    blocks.append(block.release_nonnull());
87
3.51M
    return true;
88
108M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::Heading>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
19.5M
{
83
19.5M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
19.5M
    if (!block)
85
19.2M
        return false;
86
322k
    blocks.append(block.release_nonnull());
87
322k
    return true;
88
19.5M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::Table>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
19.2M
{
83
19.2M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
19.2M
    if (!block)
85
19.1M
        return false;
86
13.3k
    blocks.append(block.release_nonnull());
87
13.3k
    return true;
88
19.2M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::HorizontalRule>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
19.1M
{
83
19.1M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
19.1M
    if (!block)
85
19.1M
        return false;
86
59.4k
    blocks.append(block.release_nonnull());
87
59.4k
    return true;
88
19.1M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::List>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
19.1M
{
83
19.1M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
19.1M
    if (!block)
85
18.9M
        return false;
86
188k
    blocks.append(block.release_nonnull());
87
188k
    return true;
88
19.1M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::CommentBlock>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
15.8M
{
83
15.8M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
15.8M
    if (!block)
85
15.7M
        return false;
86
37.3k
    blocks.append(block.release_nonnull());
87
37.3k
    return true;
88
15.8M
}
ContainerBlock.cpp:bool Markdown::try_parse_block<Markdown::BlockQuote>(Markdown::LineIterator&, AK::Vector<AK::NonnullOwnPtr<Markdown::Block>, 0ul>&)
Line
Count
Source
82
15.7M
{
83
15.7M
    OwnPtr<BlockType> block = BlockType::parse(lines);
84
15.7M
    if (!block)
85
12.8M
        return false;
86
2.89M
    blocks.append(block.release_nonnull());
87
2.89M
    return true;
88
15.7M
}
89
90
OwnPtr<ContainerBlock> ContainerBlock::parse(LineIterator& lines)
91
3.46M
{
92
3.46M
    Vector<NonnullOwnPtr<Block>> blocks;
93
94
3.46M
    StringBuilder paragraph_text;
95
3.46M
    Heading* current_section = nullptr;
96
97
21.6M
    auto flush_paragraph = [&] {
98
21.6M
        if (paragraph_text.is_empty())
99
18.6M
            return;
100
2.98M
        auto paragraph = make<Paragraph>(Text::parse(paragraph_text.to_byte_string()));
101
2.98M
        blocks.append(move(paragraph));
102
2.98M
        paragraph_text.clear();
103
2.98M
    };
104
105
3.46M
    bool has_blank_lines = false;
106
3.46M
    bool has_trailing_blank_lines = false;
107
108
38.2M
    while (true) {
109
38.2M
        if (lines.is_end())
110
3.46M
            break;
111
112
34.8M
        if ((*lines).is_whitespace()) {
113
15.2M
            has_trailing_blank_lines = true;
114
15.2M
            ++lines;
115
116
15.2M
            flush_paragraph();
117
15.2M
            continue;
118
19.5M
        } else {
119
19.5M
            has_blank_lines = has_blank_lines || has_trailing_blank_lines;
120
19.5M
        }
121
122
19.5M
        bool heading = false;
123
19.5M
        if ((heading = try_parse_block<Heading>(lines, blocks)))
124
322k
            current_section = dynamic_cast<Heading*>(blocks.last().ptr());
125
126
19.5M
        bool any = heading
127
19.5M
            || try_parse_block<Table>(lines, blocks)
128
19.5M
            || try_parse_block<HorizontalRule>(lines, blocks)
129
19.5M
            || try_parse_block<List>(lines, blocks)
130
            // CodeBlock needs to know the current section's name for proper indentation
131
19.5M
            || try_parse_block<CodeBlock>(lines, blocks, current_section)
132
19.5M
            || try_parse_block<CommentBlock>(lines, blocks)
133
19.5M
            || try_parse_block<BlockQuote>(lines, blocks);
134
135
19.5M
        if (any) {
136
6.65M
            if (!paragraph_text.is_empty()) {
137
2.89M
                auto last_block = blocks.take_last();
138
2.89M
                flush_paragraph();
139
2.89M
                blocks.append(move(last_block));
140
2.89M
            }
141
6.65M
            continue;
142
6.65M
        }
143
144
12.8M
        if (!paragraph_text.is_empty())
145
9.89M
            paragraph_text.append('\n');
146
12.8M
        paragraph_text.append(*lines++);
147
12.8M
    }
148
149
3.46M
    flush_paragraph();
150
151
3.46M
    return make<ContainerBlock>(move(blocks), has_blank_lines, has_trailing_blank_lines);
152
3.46M
}
153
154
}