mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-10 13:00:29 +03:00
6e7459322d
Having an alias function that only wraps another one is silly, and keeping the more obvious name should flush out more uses of deprecated strings. No behavior change.
203 lines
5.2 KiB
C++
203 lines
5.2 KiB
C++
/*
|
|
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
|
|
* Copyright (c) 2022, Peter Elliott <pelliott@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Forward.h>
|
|
#include <AK/StringBuilder.h>
|
|
#include <LibJS/MarkupGenerator.h>
|
|
#include <LibMarkdown/CodeBlock.h>
|
|
#include <LibMarkdown/Visitor.h>
|
|
#include <LibRegex/Regex.h>
|
|
|
|
namespace Markdown {
|
|
|
|
DeprecatedString CodeBlock::render_to_html(bool) const
|
|
{
|
|
StringBuilder builder;
|
|
|
|
builder.append("<pre>"sv);
|
|
|
|
if (m_style.length() >= 2)
|
|
builder.append("<strong>"sv);
|
|
else if (m_style.length() >= 2)
|
|
builder.append("<em>"sv);
|
|
|
|
if (m_language.is_empty())
|
|
builder.append("<code>"sv);
|
|
else
|
|
builder.appendff("<code class=\"language-{}\">", escape_html_entities(m_language));
|
|
|
|
if (m_language == "js") {
|
|
auto html_or_error = JS::MarkupGenerator::html_from_source(m_code);
|
|
if (html_or_error.is_error()) {
|
|
warnln("Could not render js code to html: {}", html_or_error.error());
|
|
builder.append(escape_html_entities(m_code));
|
|
} else {
|
|
builder.append(html_or_error.release_value());
|
|
}
|
|
} else {
|
|
builder.append(escape_html_entities(m_code));
|
|
}
|
|
|
|
builder.append("</code>"sv);
|
|
|
|
if (m_style.length() >= 2)
|
|
builder.append("</strong>"sv);
|
|
else if (m_style.length() >= 2)
|
|
builder.append("</em>"sv);
|
|
|
|
builder.append("</pre>\n"sv);
|
|
|
|
return builder.to_deprecated_string();
|
|
}
|
|
|
|
Vector<DeprecatedString> CodeBlock::render_lines_for_terminal(size_t) const
|
|
{
|
|
Vector<DeprecatedString> lines;
|
|
|
|
// Do not indent too much if we are in the synopsis
|
|
auto indentation = " "sv;
|
|
if (m_current_section != nullptr) {
|
|
auto current_section_name = m_current_section->render_lines_for_terminal()[0];
|
|
if (current_section_name.contains("SYNOPSIS"sv))
|
|
indentation = " "sv;
|
|
}
|
|
|
|
for (auto const& line : m_code.split('\n'))
|
|
lines.append(DeprecatedString::formatted("{}{}", indentation, line));
|
|
lines.append("");
|
|
|
|
return lines;
|
|
}
|
|
|
|
RecursionDecision CodeBlock::walk(Visitor& visitor) const
|
|
{
|
|
RecursionDecision rd = visitor.visit(*this);
|
|
if (rd != RecursionDecision::Recurse)
|
|
return rd;
|
|
|
|
rd = visitor.visit(m_code);
|
|
if (rd != RecursionDecision::Recurse)
|
|
return rd;
|
|
|
|
// Don't recurse on m_language and m_style.
|
|
|
|
// Normalize return value.
|
|
return RecursionDecision::Continue;
|
|
}
|
|
|
|
static Regex<ECMA262> open_fence_re("^ {0,3}(([\\`\\~])\\2{2,})\\s*([\\*_]*)\\s*([^\\*_\\s]*).*$");
|
|
static Regex<ECMA262> close_fence_re("^ {0,3}(([\\`\\~])\\2{2,})\\s*$");
|
|
|
|
static Optional<int> line_block_prefix(StringView const& line)
|
|
{
|
|
int characters = 0;
|
|
int indents = 0;
|
|
|
|
for (char ch : line) {
|
|
if (indents == 4)
|
|
break;
|
|
|
|
if (ch == ' ') {
|
|
++characters;
|
|
++indents;
|
|
} else if (ch == '\t') {
|
|
++characters;
|
|
indents = 4;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (indents == 4)
|
|
return characters;
|
|
|
|
return {};
|
|
}
|
|
|
|
OwnPtr<CodeBlock> CodeBlock::parse(LineIterator& lines, Heading* current_section)
|
|
{
|
|
if (lines.is_end())
|
|
return {};
|
|
|
|
StringView line = *lines;
|
|
if (open_fence_re.match(line).success)
|
|
return parse_backticks(lines, current_section);
|
|
|
|
if (line_block_prefix(line).has_value())
|
|
return parse_indent(lines);
|
|
|
|
return {};
|
|
}
|
|
|
|
OwnPtr<CodeBlock> CodeBlock::parse_backticks(LineIterator& lines, Heading* current_section)
|
|
{
|
|
StringView line = *lines;
|
|
|
|
// Our Markdown extension: we allow
|
|
// specifying a style and a language
|
|
// for a code block, like so:
|
|
//
|
|
// ```**sh**
|
|
// $ echo hello friends!
|
|
// ````
|
|
//
|
|
// The code block will be made bold,
|
|
// and if possible syntax-highlighted
|
|
// as appropriate for a shell script.
|
|
|
|
auto matches = open_fence_re.match(line).capture_group_matches[0];
|
|
auto fence = matches[0].view.string_view();
|
|
auto style = matches[2].view.string_view();
|
|
auto language = matches[3].view.string_view();
|
|
|
|
++lines;
|
|
|
|
StringBuilder builder;
|
|
|
|
while (true) {
|
|
if (lines.is_end())
|
|
break;
|
|
line = *lines;
|
|
++lines;
|
|
|
|
auto close_match = close_fence_re.match(line);
|
|
if (close_match.success) {
|
|
auto close_fence = close_match.capture_group_matches[0][0].view.string_view();
|
|
if (close_fence[0] == fence[0] && close_fence.length() >= fence.length())
|
|
break;
|
|
}
|
|
builder.append(line);
|
|
builder.append('\n');
|
|
}
|
|
|
|
return make<CodeBlock>(language, style, builder.to_deprecated_string(), current_section);
|
|
}
|
|
|
|
OwnPtr<CodeBlock> CodeBlock::parse_indent(LineIterator& lines)
|
|
{
|
|
StringBuilder builder;
|
|
|
|
while (true) {
|
|
if (lines.is_end())
|
|
break;
|
|
StringView line = *lines;
|
|
|
|
auto prefix_length = line_block_prefix(line);
|
|
if (!prefix_length.has_value())
|
|
break;
|
|
|
|
line = line.substring_view(prefix_length.value());
|
|
++lines;
|
|
|
|
builder.append(line);
|
|
builder.append('\n');
|
|
}
|
|
|
|
return make<CodeBlock>("", "", builder.to_deprecated_string(), nullptr);
|
|
}
|
|
}
|