/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 | | } |