diff options
author | Peter Elliott <pelliott@ualberta.ca> | 2021-09-10 00:45:45 -0600 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-09-12 12:17:16 +0200 |
commit | 9c0563cc1026dc23da8639100cd782374d9debe6 (patch) | |
tree | 06ce2cef1d86da7087fc1c3149a3bbd9d8facc59 /Userland | |
parent | af5a07399ee1a90d9a7bd1d8986d610f505f7615 (diff) | |
download | serenity-9c0563cc1026dc23da8639100cd782374d9debe6.zip |
LibMarkdown: Implement hard and soft line breaks
Hard line breaks insert a <br /> when two spaces are at the end of a
line. soft line breaks are just regular newlines, but whitespace is now
stripped before and after them
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibMarkdown/Document.cpp | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibMarkdown/Text.cpp | 61 | ||||
-rw-r--r-- | Userland/Libraries/LibMarkdown/Text.h | 13 |
3 files changed, 75 insertions, 3 deletions
diff --git a/Userland/Libraries/LibMarkdown/Document.cpp b/Userland/Libraries/LibMarkdown/Document.cpp index 3e91b9f67f..059bbc97c9 100644 --- a/Userland/Libraries/LibMarkdown/Document.cpp +++ b/Userland/Libraries/LibMarkdown/Document.cpp @@ -92,6 +92,7 @@ OwnPtr<Document> Document::parse(const StringView& str) if ((*lines).is_empty()) { ++lines; + flush_paragraph(); continue; } @@ -108,8 +109,9 @@ OwnPtr<Document> Document::parse(const StringView& str) continue; } + if (!paragraph_text.is_empty()) + paragraph_text.append("\n"); paragraph_text.append(*lines++); - paragraph_text.append("\n"); } flush_paragraph(); diff --git a/Userland/Libraries/LibMarkdown/Text.cpp b/Userland/Libraries/LibMarkdown/Text.cpp index 583b62bff4..71995fc7fb 100644 --- a/Userland/Libraries/LibMarkdown/Text.cpp +++ b/Userland/Libraries/LibMarkdown/Text.cpp @@ -58,6 +58,20 @@ size_t Text::CodeNode::terminal_length() const return code->terminal_length(); } +void Text::BreakNode::render_to_html(StringBuilder& builder) const +{ + builder.append("<br />"); +} + +void Text::BreakNode::render_for_terminal(StringBuilder&) const +{ +} + +size_t Text::BreakNode::terminal_length() const +{ + return 0; +} + void Text::TextNode::render_to_html(StringBuilder& builder) const { builder.append(escape_html_entities(text)); @@ -212,6 +226,8 @@ Vector<Text::Token> Text::tokenize(StringView const& str) flush_run(false, false, false, false, false); }; + bool in_space = false; + for (size_t offset = 0; offset < str.length(); ++offset) { auto has = [&](StringView const& seq) { if (offset + seq.length() > str.length()) @@ -229,6 +245,10 @@ Vector<Text::Token> Text::tokenize(StringView const& str) }; char ch = str[offset]; + if (ch != ' ' && in_space) { + flush_token(); + in_space = false; + } if (ch == '\\' && offset + 1 < str.length()) { current_token.append(str[offset + 1]); @@ -249,6 +269,12 @@ Vector<Text::Token> Text::tokenize(StringView const& str) true); offset = run_offset - 1; + } else if (ch == ' ') { + if (!in_space) { + flush_token(); + in_space = true; + } + current_token.append(ch); } else if (has("\n")) { expect("\n"); } else if (has("[")) { @@ -272,7 +298,11 @@ NonnullOwnPtr<Text::MultiNode> Text::parse_sequence(Vector<Token>::ConstIterator auto node = make<MultiNode>(); for (; !tokens.is_end(); ++tokens) { - if (tokens->is_run) { + if (tokens->is_space()) { + node->children.append(parse_break(tokens)); + } else if (*tokens == "\n") { + node->children.append(parse_newline(tokens)); + } else if (tokens->is_run) { switch (tokens->run_char()) { case '*': case '_': @@ -299,6 +329,29 @@ NonnullOwnPtr<Text::MultiNode> Text::parse_sequence(Vector<Token>::ConstIterator return node; } +NonnullOwnPtr<Text::Node> Text::parse_break(Vector<Token>::ConstIterator& tokens) +{ + auto next_tok = tokens + 1; + if (next_tok.is_end() || *next_tok != "\n") + return make<TextNode>(tokens->data); + + if (tokens->data.length() >= 2) + return make<BreakNode>(); + + return make<MultiNode>(); +} + +NonnullOwnPtr<Text::Node> Text::parse_newline(Vector<Token>::ConstIterator& tokens) +{ + auto node = make<TextNode>(tokens->data); + auto next_tok = tokens + 1; + if (!next_tok.is_end() && next_tok->is_space()) + // Skip whitespace after newline. + ++tokens; + + return node; +} + bool Text::can_open(Token const& opening) { return (opening.run_char() == '*' && opening.left_flanking) || (opening.run_char() == '_' && opening.left_flanking && (!opening.right_flanking || opening.punct_before)); @@ -325,7 +378,11 @@ NonnullOwnPtr<Text::Node> Text::parse_emph(Vector<Token>::ConstIterator& tokens, auto child = make<MultiNode>(); for (++tokens; !tokens.is_end(); ++tokens) { - if (tokens->is_run) { + if (tokens->is_space()) { + child->children.append(parse_break(tokens)); + } else if (*tokens == "\n") { + child->children.append(parse_newline(tokens)); + } else if (tokens->is_run) { if (can_close_for(opening, *tokens)) { return make<EmphasisNode>(opening.run_length() >= 2, move(child)); } diff --git a/Userland/Libraries/LibMarkdown/Text.h b/Userland/Libraries/LibMarkdown/Text.h index fdb7f3ed63..d7715412cf 100644 --- a/Userland/Libraries/LibMarkdown/Text.h +++ b/Userland/Libraries/LibMarkdown/Text.h @@ -55,6 +55,13 @@ public: virtual size_t terminal_length() const override; }; + class BreakNode : public Node { + public: + virtual void render_to_html(StringBuilder& builder) const override; + virtual void render_for_terminal(StringBuilder& builder) const override; + virtual size_t terminal_length() const override; + }; + class TextNode : public Node { public: String text; @@ -128,6 +135,10 @@ private: VERIFY(is_run); return data.length(); } + bool is_space() const + { + return data[0] == ' '; + } bool operator==(StringView const& str) const { return str == data; } }; @@ -137,6 +148,8 @@ private: static bool can_close_for(Token const& opening, Token const& closing); static NonnullOwnPtr<MultiNode> parse_sequence(Vector<Token>::ConstIterator& tokens, bool in_link); + static NonnullOwnPtr<Node> parse_break(Vector<Token>::ConstIterator& tokens); + static NonnullOwnPtr<Node> parse_newline(Vector<Token>::ConstIterator& tokens); static NonnullOwnPtr<Node> parse_emph(Vector<Token>::ConstIterator& tokens, bool in_link); static NonnullOwnPtr<Node> parse_code(Vector<Token>::ConstIterator& tokens); static NonnullOwnPtr<Node> parse_link(Vector<Token>::ConstIterator& tokens); |