summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorPeter Elliott <pelliott@ualberta.ca>2021-09-10 00:45:45 -0600
committerAndreas Kling <kling@serenityos.org>2021-09-12 12:17:16 +0200
commit9c0563cc1026dc23da8639100cd782374d9debe6 (patch)
tree06ce2cef1d86da7087fc1c3149a3bbd9d8facc59 /Userland
parentaf5a07399ee1a90d9a7bd1d8986d610f505f7615 (diff)
downloadserenity-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.cpp4
-rw-r--r--Userland/Libraries/LibMarkdown/Text.cpp61
-rw-r--r--Userland/Libraries/LibMarkdown/Text.h13
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);