Serenity Operating System

LibMarkdown: Render lines to terminal instead of a single string

With this patch, the blocks in a markdown document render a vector of
lines. These lines get concatenated in Document::render_to_terminal, so
this does not change any external APIs of LibMarkdown.

This change makes it possible to indent individual lines in the rendered
markdown. So, rendering blockquotes in a similar way to code blocks :^)

authored by

Arda Cinar and committed by
Andreas Kling
5cc984d7 7a4b912e

+85 -55
+1 -1
Userland/Libraries/LibMarkdown/Block.h
··· 19 19 virtual ~Block() = default; 20 20 21 21 virtual DeprecatedString render_to_html(bool tight = false) const = 0; 22 - virtual DeprecatedString render_for_terminal(size_t view_width = 0) const = 0; 22 + virtual Vector<DeprecatedString> render_lines_for_terminal(size_t view_width = 0) const = 0; 23 23 virtual RecursionDecision walk(Visitor&) const = 0; 24 24 }; 25 25
+4 -3
Userland/Libraries/LibMarkdown/BlockQuote.cpp
··· 5 5 */ 6 6 7 7 #include <AK/StringBuilder.h> 8 + #include <AK/Vector.h> 8 9 #include <LibMarkdown/BlockQuote.h> 9 10 #include <LibMarkdown/Visitor.h> 10 11 ··· 19 20 return builder.build(); 20 21 } 21 22 22 - DeprecatedString BlockQuote::render_for_terminal(size_t view_width) const 23 + Vector<DeprecatedString> BlockQuote::render_lines_for_terminal(size_t view_width) const 23 24 { 24 - // FIXME: Rewrite the whole terminal renderer to make blockquote rendering possible 25 - return m_contents->render_for_terminal(view_width); 25 + // FIXME: Indent lines inside the blockquote 26 + return m_contents->render_lines_for_terminal(view_width); 26 27 } 27 28 28 29 RecursionDecision BlockQuote::walk(Visitor& visitor) const
+1 -1
Userland/Libraries/LibMarkdown/BlockQuote.h
··· 22 22 virtual ~BlockQuote() override = default; 23 23 24 24 virtual DeprecatedString render_to_html(bool tight = false) const override; 25 - virtual DeprecatedString render_for_terminal(size_t view_width = 0) const override; 25 + virtual Vector<DeprecatedString> render_lines_for_terminal(size_t view_width = 0) const override; 26 26 virtual RecursionDecision walk(Visitor&) const override; 27 27 28 28 static OwnPtr<BlockQuote> parse(LineIterator& lines);
+14 -12
Userland/Libraries/LibMarkdown/CodeBlock.cpp
··· 5 5 * SPDX-License-Identifier: BSD-2-Clause 6 6 */ 7 7 8 + #include <AK/Forward.h> 8 9 #include <AK/StringBuilder.h> 9 10 #include <LibJS/MarkupGenerator.h> 10 11 #include <LibMarkdown/CodeBlock.h> ··· 53 54 return builder.build(); 54 55 } 55 56 56 - DeprecatedString CodeBlock::render_for_terminal(size_t) const 57 + Vector<DeprecatedString> CodeBlock::render_lines_for_terminal(size_t) const 57 58 { 58 - StringBuilder builder; 59 + Vector<DeprecatedString> lines; 59 60 60 - for (auto const& line : m_code.split('\n')) { 61 - // Do not indent too much if we are in the synopsis 62 - if (!(m_current_section && m_current_section->render_for_terminal().contains("SYNOPSIS"sv))) 63 - builder.append(" "sv); 61 + // Do not indent too much if we are in the synopsis 62 + auto indentation = " "sv; 63 + if (m_current_section != nullptr) { 64 + auto current_section_name = m_current_section->render_lines_for_terminal()[0]; 65 + if (current_section_name.contains("SYNOPSIS"sv)) 66 + indentation = " "sv; 67 + } 64 68 65 - builder.append(" "sv); 66 - builder.append(line); 67 - builder.append("\n"sv); 68 - } 69 - builder.append("\n"sv); 69 + for (auto const& line : m_code.split('\n')) 70 + lines.append(DeprecatedString::formatted("{}{}", indentation, line)); 71 + lines.append(""); 70 72 71 - return builder.build(); 73 + return lines; 72 74 } 73 75 74 76 RecursionDecision CodeBlock::walk(Visitor& visitor) const
+1 -1
Userland/Libraries/LibMarkdown/CodeBlock.h
··· 27 27 virtual ~CodeBlock() override = default; 28 28 29 29 virtual DeprecatedString render_to_html(bool tight = false) const override; 30 - virtual DeprecatedString render_for_terminal(size_t view_width = 0) const override; 30 + virtual Vector<DeprecatedString> render_lines_for_terminal(size_t view_width = 0) const override; 31 31 virtual RecursionDecision walk(Visitor&) const override; 32 32 static OwnPtr<CodeBlock> parse(LineIterator& lines, Heading* current_section); 33 33
+3 -2
Userland/Libraries/LibMarkdown/CommentBlock.cpp
··· 4 4 * SPDX-License-Identifier: BSD-2-Clause 5 5 */ 6 6 7 + #include <AK/Forward.h> 7 8 #include <AK/StringBuilder.h> 8 9 #include <LibMarkdown/CommentBlock.h> 9 10 #include <LibMarkdown/Visitor.h> ··· 22 23 return builder.build(); 23 24 } 24 25 25 - DeprecatedString CommentBlock::render_for_terminal(size_t) const 26 + Vector<DeprecatedString> CommentBlock::render_lines_for_terminal(size_t) const 26 27 { 27 - return ""; 28 + return Vector<DeprecatedString> {}; 28 29 } 29 30 30 31 RecursionDecision CommentBlock::walk(Visitor& visitor) const
+1 -1
Userland/Libraries/LibMarkdown/CommentBlock.h
··· 23 23 virtual ~CommentBlock() override = default; 24 24 25 25 virtual DeprecatedString render_to_html(bool tight = false) const override; 26 - virtual DeprecatedString render_for_terminal(size_t view_width = 0) const override; 26 + virtual Vector<DeprecatedString> render_lines_for_terminal(size_t view_width = 0) const override; 27 27 virtual RecursionDecision walk(Visitor&) const override; 28 28 static OwnPtr<CommentBlock> parse(LineIterator& lines); 29 29
+6 -5
Userland/Libraries/LibMarkdown/ContainerBlock.cpp
··· 4 4 * SPDX-License-Identifier: BSD-2-Clause 5 5 */ 6 6 7 + #include <AK/Forward.h> 7 8 #include <LibMarkdown/BlockQuote.h> 8 9 #include <LibMarkdown/CodeBlock.h> 9 10 #include <LibMarkdown/ContainerBlock.h> ··· 39 40 return builder.build(); 40 41 } 41 42 42 - DeprecatedString ContainerBlock::render_for_terminal(size_t view_width) const 43 + Vector<DeprecatedString> ContainerBlock::render_lines_for_terminal(size_t view_width) const 43 44 { 44 - StringBuilder builder; 45 + Vector<DeprecatedString> lines; 45 46 46 47 for (auto& block : m_blocks) { 47 - auto s = block.render_for_terminal(view_width); 48 - builder.append(s); 48 + for (auto& line : block.render_lines_for_terminal(view_width)) 49 + lines.append(move(line)); 49 50 } 50 51 51 - return builder.build(); 52 + return lines; 52 53 } 53 54 54 55 RecursionDecision ContainerBlock::walk(Visitor& visitor) const
+1 -1
Userland/Libraries/LibMarkdown/ContainerBlock.h
··· 27 27 virtual ~ContainerBlock() override = default; 28 28 29 29 virtual DeprecatedString render_to_html(bool tight = false) const override; 30 - virtual DeprecatedString render_for_terminal(size_t view_width = 0) const override; 30 + virtual Vector<DeprecatedString> render_lines_for_terminal(size_t view_width = 0) const override; 31 31 virtual RecursionDecision walk(Visitor&) const override; 32 32 33 33 static OwnPtr<ContainerBlock> parse(LineIterator& lines);
+7 -1
Userland/Libraries/LibMarkdown/Document.cpp
··· 45 45 46 46 DeprecatedString Document::render_for_terminal(size_t view_width) const 47 47 { 48 - return m_container->render_for_terminal(view_width); 48 + StringBuilder builder; 49 + for (auto& line : m_container->render_lines_for_terminal(view_width)) { 50 + builder.append(line); 51 + builder.append("\n"sv); 52 + } 53 + 54 + return builder.build(); 49 55 } 50 56 51 57 RecursionDecision Document::walk(Visitor& visitor) const
+4 -4
Userland/Libraries/LibMarkdown/Heading.cpp
··· 15 15 return DeprecatedString::formatted("<h{}>{}</h{}>\n", m_level, m_text.render_to_html(), m_level); 16 16 } 17 17 18 - DeprecatedString Heading::render_for_terminal(size_t) const 18 + Vector<DeprecatedString> Heading::render_lines_for_terminal(size_t) const 19 19 { 20 20 StringBuilder builder; 21 21 ··· 24 24 case 1: 25 25 case 2: 26 26 builder.append(m_text.render_for_terminal().to_uppercase()); 27 - builder.append("\033[0m\n"sv); 27 + builder.append("\033[0m"sv); 28 28 break; 29 29 default: 30 30 builder.append(m_text.render_for_terminal()); 31 - builder.append("\033[0m\n"sv); 31 + builder.append("\033[0m"sv); 32 32 break; 33 33 } 34 34 35 - return builder.build(); 35 + return Vector<DeprecatedString> { builder.build() }; 36 36 } 37 37 38 38 RecursionDecision Heading::walk(Visitor& visitor) const
+1 -1
Userland/Libraries/LibMarkdown/Heading.h
··· 27 27 virtual ~Heading() override = default; 28 28 29 29 virtual DeprecatedString render_to_html(bool tight = false) const override; 30 - virtual DeprecatedString render_for_terminal(size_t view_width = 0) const override; 30 + virtual Vector<DeprecatedString> render_lines_for_terminal(size_t view_width = 0) const override; 31 31 virtual RecursionDecision walk(Visitor&) const override; 32 32 static OwnPtr<Heading> parse(LineIterator& lines); 33 33
+2 -2
Userland/Libraries/LibMarkdown/HorizontalRule.cpp
··· 17 17 return "<hr />\n"; 18 18 } 19 19 20 - DeprecatedString HorizontalRule::render_for_terminal(size_t view_width) const 20 + Vector<DeprecatedString> HorizontalRule::render_lines_for_terminal(size_t view_width) const 21 21 { 22 22 StringBuilder builder(view_width + 1); 23 23 for (size_t i = 0; i < view_width; ++i) 24 24 builder.append('-'); 25 25 builder.append("\n\n"sv); 26 - return builder.to_deprecated_string(); 26 + return Vector<DeprecatedString> { builder.to_deprecated_string() }; 27 27 } 28 28 29 29 RecursionDecision HorizontalRule::walk(Visitor& visitor) const
+1 -1
Userland/Libraries/LibMarkdown/HorizontalRule.h
··· 21 21 virtual ~HorizontalRule() override = default; 22 22 23 23 virtual DeprecatedString render_to_html(bool tight = false) const override; 24 - virtual DeprecatedString render_for_terminal(size_t view_width = 0) const override; 24 + virtual Vector<DeprecatedString> render_lines_for_terminal(size_t view_width = 0) const override; 25 25 virtual RecursionDecision walk(Visitor&) const override; 26 26 static OwnPtr<HorizontalRule> parse(LineIterator& lines); 27 27 };
+20 -4
Userland/Libraries/LibMarkdown/List.cpp
··· 5 5 * SPDX-License-Identifier: BSD-2-Clause 6 6 */ 7 7 8 + #include <AK/Forward.h> 8 9 #include <AK/StringBuilder.h> 9 10 #include <LibMarkdown/List.h> 10 11 #include <LibMarkdown/Paragraph.h> ··· 37 38 return builder.build(); 38 39 } 39 40 40 - DeprecatedString List::render_for_terminal(size_t) const 41 + Vector<DeprecatedString> List::render_lines_for_terminal(size_t view_width) const 41 42 { 42 - StringBuilder builder; 43 + Vector<DeprecatedString> lines; 43 44 44 45 int i = 0; 45 46 for (auto& item : m_items) { 47 + auto item_lines = item->render_lines_for_terminal(view_width); 48 + auto first_line = item_lines.take_first(); 49 + 50 + StringBuilder builder; 46 51 builder.append(" "sv); 47 52 if (m_is_ordered) 48 53 builder.appendff("{}.", ++i); 49 54 else 50 55 builder.append('*'); 51 - builder.append(item->render_for_terminal()); 56 + auto item_indentation = builder.length(); 57 + 58 + builder.append(first_line); 59 + 60 + lines.append(builder.build()); 61 + 62 + for (auto& line : item_lines) { 63 + builder.clear(); 64 + builder.append(DeprecatedString::repeated(' ', item_indentation)); 65 + builder.append(line); 66 + lines.append(builder.build()); 67 + } 52 68 } 53 69 54 - return builder.build(); 70 + return lines; 55 71 } 56 72 57 73 RecursionDecision List::walk(Visitor& visitor) const
+1 -1
Userland/Libraries/LibMarkdown/List.h
··· 26 26 virtual ~List() override = default; 27 27 28 28 virtual DeprecatedString render_to_html(bool tight = false) const override; 29 - virtual DeprecatedString render_for_terminal(size_t view_width = 0) const override; 29 + virtual Vector<DeprecatedString> render_lines_for_terminal(size_t view_width = 0) const override; 30 30 virtual RecursionDecision walk(Visitor&) const override; 31 31 32 32 static OwnPtr<List> parse(LineIterator& lines);
+3 -6
Userland/Libraries/LibMarkdown/Paragraph.cpp
··· 4 4 * SPDX-License-Identifier: BSD-2-Clause 5 5 */ 6 6 7 + #include <AK/Forward.h> 7 8 #include <AK/StringBuilder.h> 8 9 #include <LibMarkdown/Paragraph.h> 9 10 #include <LibMarkdown/Visitor.h> ··· 27 28 return builder.build(); 28 29 } 29 30 30 - DeprecatedString Paragraph::render_for_terminal(size_t) const 31 + Vector<DeprecatedString> Paragraph::render_lines_for_terminal(size_t) const 31 32 { 32 - StringBuilder builder; 33 - builder.append(" "sv); 34 - builder.append(m_text.render_for_terminal()); 35 - builder.append("\n\n"sv); 36 - return builder.build(); 33 + return Vector<DeprecatedString> { DeprecatedString::formatted(" {}", m_text.render_for_terminal()), "" }; 37 34 } 38 35 39 36 RecursionDecision Paragraph::walk(Visitor& visitor) const
+1 -1
Userland/Libraries/LibMarkdown/Paragraph.h
··· 24 24 virtual ~Paragraph() override = default; 25 25 26 26 virtual DeprecatedString render_to_html(bool tight = false) const override; 27 - virtual DeprecatedString render_for_terminal(size_t view_width = 0) const override; 27 + virtual Vector<DeprecatedString> render_lines_for_terminal(size_t view_width = 0) const override; 28 28 virtual RecursionDecision walk(Visitor&) const override; 29 29 30 30 private:
+12 -6
Userland/Libraries/LibMarkdown/Table.cpp
··· 6 6 7 7 #include <AK/Debug.h> 8 8 #include <AK/StringBuilder.h> 9 + #include <AK/Vector.h> 9 10 #include <LibMarkdown/Table.h> 10 11 #include <LibMarkdown/Visitor.h> 11 12 12 13 namespace Markdown { 13 14 14 - DeprecatedString Table::render_for_terminal(size_t view_width) const 15 + Vector<DeprecatedString> Table::render_lines_for_terminal(size_t view_width) const 15 16 { 16 17 auto unit_width_length = view_width == 0 ? 4 : ((float)(view_width - m_columns.size()) / (float)m_total_width); 17 18 StringBuilder builder; 19 + Vector<DeprecatedString> lines; 18 20 19 21 auto write_aligned = [&](auto const& text, auto width, auto alignment) { 20 22 size_t original_length = text.terminal_length(); ··· 42 44 write_aligned(col.header, width, col.alignment); 43 45 } 44 46 45 - builder.append('\n'); 47 + lines.append(builder.build()); 48 + builder.clear(); 49 + 46 50 for (size_t i = 0; i < view_width; ++i) 47 51 builder.append('-'); 48 - builder.append('\n'); 52 + lines.append(builder.build()); 53 + builder.clear(); 49 54 50 55 for (size_t i = 0; i < m_row_count; ++i) { 51 56 bool first = true; ··· 60 65 size_t width = col.relative_width * unit_width_length; 61 66 write_aligned(cell, width, col.alignment); 62 67 } 63 - builder.append('\n'); 68 + lines.append(builder.build()); 69 + builder.clear(); 64 70 } 65 71 66 - builder.append('\n'); 72 + lines.append(""); 67 73 68 - return builder.to_deprecated_string(); 74 + return lines; 69 75 } 70 76 71 77 DeprecatedString Table::render_to_html(bool) const
+1 -1
Userland/Libraries/LibMarkdown/Table.h
··· 35 35 virtual ~Table() override = default; 36 36 37 37 virtual DeprecatedString render_to_html(bool tight = false) const override; 38 - virtual DeprecatedString render_for_terminal(size_t view_width = 0) const override; 38 + virtual Vector<DeprecatedString> render_lines_for_terminal(size_t view_width = 0) const override; 39 39 virtual RecursionDecision walk(Visitor&) const override; 40 40 static OwnPtr<Table> parse(LineIterator& lines); 41 41