mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 23:17:46 +00:00
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
This commit is contained in:
parent
af5a07399e
commit
9c0563cc10
3 changed files with 75 additions and 3 deletions
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue