diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index ef0ebbd0b2..02ac2acd9d 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -437,7 +437,6 @@ if (BUILD_LAGOM) JS Line Locale - Markdown Protocol Regex RIFF @@ -562,7 +561,6 @@ if (BUILD_LAGOM) LibCompress LibGfx LibLocale - LibMarkdown LibSQL LibTest LibTextCodec diff --git a/Meta/Lagom/Fuzzers/FuzzMarkdown.cpp b/Meta/Lagom/Fuzzers/FuzzMarkdown.cpp deleted file mode 100644 index e29a831487..0000000000 --- a/Meta/Lagom/Fuzzers/FuzzMarkdown.cpp +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include - -extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) -{ - AK::set_debug_enabled(false); - auto markdown = StringView(static_cast(data), size); - (void)Markdown::Document::parse(markdown); - return 0; -} diff --git a/Meta/Lagom/Fuzzers/fuzzers.cmake b/Meta/Lagom/Fuzzers/fuzzers.cmake index 6ac49a5bb8..3492052235 100644 --- a/Meta/Lagom/Fuzzers/fuzzers.cmake +++ b/Meta/Lagom/Fuzzers/fuzzers.cmake @@ -23,7 +23,6 @@ set(FUZZER_TARGETS JsonParser LzmaDecompression LzmaRoundtrip - Markdown MatroskaReader MD5 MP3Loader @@ -91,7 +90,6 @@ set(FUZZER_DEPENDENCIES_JPEGLoader LibGfx) set(FUZZER_DEPENDENCIES_Js LibJS) set(FUZZER_DEPENDENCIES_LzmaDecompression LibArchive LibCompress) set(FUZZER_DEPENDENCIES_LzmaRoundtrip LibCompress) -set(FUZZER_DEPENDENCIES_Markdown LibMarkdown) set(FUZZER_DEPENDENCIES_MatroskaReader LibVideo) set(FUZZER_DEPENDENCIES_MD5 LibCrypto) set(FUZZER_DEPENDENCIES_MP3Loader LibAudio) diff --git a/Meta/gn/secondary/Ladybird/BUILD.gn b/Meta/gn/secondary/Ladybird/BUILD.gn index d04a772169..c78a16c6bd 100644 --- a/Meta/gn/secondary/Ladybird/BUILD.gn +++ b/Meta/gn/secondary/Ladybird/BUILD.gn @@ -392,7 +392,6 @@ if (current_os != "mac") { "//Userland/Libraries/LibImageDecoderClient", "//Userland/Libraries/LibJS", "//Userland/Libraries/LibLine", - "//Userland/Libraries/LibMarkdown", "//Userland/Libraries/LibProtocol", "//Userland/Libraries/LibRIFF", "//Userland/Libraries/LibRegex", @@ -427,7 +426,6 @@ if (current_os != "mac") { "$root_out_dir/lib/liblagom-ipc.dylib", "$root_out_dir/lib/liblagom-js.dylib", "$root_out_dir/lib/liblagom-line.dylib", - "$root_out_dir/lib/liblagom-markdown.dylib", "$root_out_dir/lib/liblagom-protocol.dylib", "$root_out_dir/lib/liblagom-regex.dylib", "$root_out_dir/lib/liblagom-riff.dylib", diff --git a/Meta/gn/secondary/Userland/Libraries/LibMarkdown/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibMarkdown/BUILD.gn deleted file mode 100644 index 1a6928f937..0000000000 --- a/Meta/gn/secondary/Userland/Libraries/LibMarkdown/BUILD.gn +++ /dev/null @@ -1,24 +0,0 @@ -shared_library("LibMarkdown") { - output_name = "markdown" - include_dirs = [ "//Userland/Libraries" ] - sources = [ - "BlockQuote.cpp", - "CodeBlock.cpp", - "CommentBlock.cpp", - "ContainerBlock.cpp", - "Document.cpp", - "Heading.cpp", - "HorizontalRule.cpp", - "LineIterator.cpp", - "List.cpp", - "Paragraph.cpp", - "Table.cpp", - "Text.cpp", - ] - deps = [ - "//AK", - "//Userland/Libraries/LibCore", - "//Userland/Libraries/LibJS", - "//Userland/Libraries/LibRegex", - ] -} diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibWeb/BUILD.gn index 866ad03435..1d5a83d856 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibWeb/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/BUILD.gn @@ -371,7 +371,6 @@ shared_library("LibWeb") { "//Userland/Libraries/LibIPC", "//Userland/Libraries/LibJS", "//Userland/Libraries/LibLocale", - "//Userland/Libraries/LibMarkdown", "//Userland/Libraries/LibRegex", "//Userland/Libraries/LibSyntax", "//Userland/Libraries/LibTLS", diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 77b0506be2..a7ede0bfe5 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -6,7 +6,6 @@ add_subdirectory(LibDiff) add_subdirectory(LibGfx) add_subdirectory(LibJS) add_subdirectory(LibLocale) -add_subdirectory(LibMarkdown) add_subdirectory(LibRegex) add_subdirectory(LibSQL) add_subdirectory(LibTest) diff --git a/Tests/LibMarkdown/CMakeLists.txt b/Tests/LibMarkdown/CMakeLists.txt deleted file mode 100644 index adf499594b..0000000000 --- a/Tests/LibMarkdown/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -include(commonmark_spec) - -set(TEST_SOURCES - TestCommonmark.cpp - TestImageSizeExtension.cpp -) - -foreach(source IN LISTS TEST_SOURCES) - serenity_test("${source}" LibMarkdown LIBS LibMarkdown) -endforeach() - -if (BUILD_LAGOM) - set_tests_properties(TestCommonmark PROPERTIES DISABLED YES) -endif() diff --git a/Tests/LibMarkdown/TestCommonmark.cpp b/Tests/LibMarkdown/TestCommonmark.cpp deleted file mode 100644 index 56372b7924..0000000000 --- a/Tests/LibMarkdown/TestCommonmark.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021, Peter Elliott - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -TEST_SETUP -{ - auto file_or_error = Core::File::open("/home/anon/Tests/commonmark.spec.json"sv, Core::File::OpenMode::Read); - if (file_or_error.is_error()) - file_or_error = Core::File::open("./commonmark.spec.json"sv, Core::File::OpenMode::Read); - VERIFY(!file_or_error.is_error()); - auto file = file_or_error.release_value(); - auto file_size = MUST(file->size()); - auto content = MUST(ByteBuffer::create_uninitialized(file_size)); - MUST(file->read_until_filled(content.bytes())); - ByteString test_data { content.bytes() }; - - auto tests = JsonParser(test_data).parse().value().as_array(); - for (size_t i = 0; i < tests.size(); ++i) { - auto testcase = tests[i].as_object(); - - auto name = ByteString::formatted("{}_ex{}_{}..{}", - testcase.get("section"sv).value(), - testcase.get("example"sv).value(), - testcase.get("start_line"sv).value(), - testcase.get("end_line"sv).value()); - - ByteString markdown = testcase.get_byte_string("markdown"sv).value(); - ByteString html = testcase.get_byte_string("html"sv).value(); - - Test::TestSuite::the().add_case(adopt_ref(*new Test::TestCase( - name, [markdown, html]() { - auto document = Markdown::Document::parse(markdown); - EXPECT_EQ(document->render_to_inline_html(), html); - }, - false))); - } -} diff --git a/Tests/LibMarkdown/TestImageSizeExtension.cpp b/Tests/LibMarkdown/TestImageSizeExtension.cpp deleted file mode 100644 index 428dc46d9c..0000000000 --- a/Tests/LibMarkdown/TestImageSizeExtension.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2022, MacDue - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include - -struct TestCase { - StringView markdown; - StringView expected_html; -}; - -static constexpr Array image_size_tests { - // No image size: - TestCase { .markdown = "![](foo.png)"sv, .expected_html = R"(

)"sv }, - // Only width given: - TestCase { .markdown = "![](foo.png =100x)"sv, .expected_html = R"(

)"sv }, - // Only height given: - TestCase { .markdown = "![](foo.png =x200)"sv, .expected_html = R"(

)"sv }, - // Both width and height given - TestCase { .markdown = "![](foo.png =50x25)"sv, .expected_html = R"(

)"sv }, - // Size contains invalid width - TestCase { .markdown = "![](foo.png =1oox50)"sv, .expected_html = R"(

)"sv }, - // Size contains invalid height - TestCase { .markdown = "![](foo.png =900xfour)"sv, .expected_html = R"(

)"sv }, -}; - -TEST_CASE(test_image_size_markdown_extension) -{ - for (auto const& test_case : image_size_tests) { - auto document = Markdown::Document::parse(test_case.markdown); - auto raw_rendered_html = document->render_to_inline_html(); - auto rendered_html = StringView(raw_rendered_html).trim_whitespace(); - EXPECT_EQ(rendered_html, test_case.expected_html); - } -} diff --git a/Userland/Libraries/CMakeLists.txt b/Userland/Libraries/CMakeLists.txt index 6a9bf7abbd..d23ac123b7 100644 --- a/Userland/Libraries/CMakeLists.txt +++ b/Userland/Libraries/CMakeLists.txt @@ -22,7 +22,6 @@ add_subdirectory(LibKeyboard) add_subdirectory(LibLine) add_subdirectory(LibLocale) add_subdirectory(LibMain) -add_subdirectory(LibMarkdown) add_subdirectory(LibProtocol) add_subdirectory(LibRegex) add_subdirectory(LibRIFF) diff --git a/Userland/Libraries/LibMarkdown/Block.h b/Userland/Libraries/LibMarkdown/Block.h deleted file mode 100644 index c764c3ba18..0000000000 --- a/Userland/Libraries/LibMarkdown/Block.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * Copyright (c) 2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -namespace Markdown { - -class Block { -public: - virtual ~Block() = default; - - virtual ByteString render_to_html(bool tight = false) const = 0; - virtual Vector render_lines_for_terminal(size_t view_width = 0) const = 0; - virtual RecursionDecision walk(Visitor&) const = 0; -}; - -} diff --git a/Userland/Libraries/LibMarkdown/BlockQuote.cpp b/Userland/Libraries/LibMarkdown/BlockQuote.cpp deleted file mode 100644 index 7066a1d71b..0000000000 --- a/Userland/Libraries/LibMarkdown/BlockQuote.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2021, Peter Elliott - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include - -namespace Markdown { - -ByteString BlockQuote::render_to_html(bool) const -{ - StringBuilder builder; - builder.append("
\n"sv); - builder.append(m_contents->render_to_html()); - builder.append("
\n"sv); - return builder.to_byte_string(); -} - -Vector BlockQuote::render_lines_for_terminal(size_t view_width) const -{ - Vector lines; - size_t child_width = view_width < 4 ? 0 : view_width - 4; - for (auto& line : m_contents->render_lines_for_terminal(child_width)) - lines.append(ByteString::formatted(" {}", line)); - - return lines; -} - -RecursionDecision BlockQuote::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - return m_contents->walk(visitor); -} - -OwnPtr
BlockQuote::parse(LineIterator& lines) -{ - lines.push_context(LineIterator::Context::block_quote()); - if (lines.is_end()) { - lines.pop_context(); - return {}; - } - - auto contents = ContainerBlock::parse(lines); - lines.pop_context(); - - if (!contents) - return {}; - - return make
(move(contents)); -} - -} diff --git a/Userland/Libraries/LibMarkdown/BlockQuote.h b/Userland/Libraries/LibMarkdown/BlockQuote.h deleted file mode 100644 index e20e4678d5..0000000000 --- a/Userland/Libraries/LibMarkdown/BlockQuote.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021, Peter Elliott - * Copyright (c) 2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include - -namespace Markdown { - -class BlockQuote final : public Block { -public: - BlockQuote(OwnPtr contents) - : m_contents(move(contents)) - { - } - virtual ~BlockQuote() override = default; - - virtual ByteString render_to_html(bool tight = false) const override; - virtual Vector render_lines_for_terminal(size_t view_width = 0) const override; - virtual RecursionDecision walk(Visitor&) const override; - - static OwnPtr
parse(LineIterator& lines); - -private: - OwnPtr m_contents; -}; - -} diff --git a/Userland/Libraries/LibMarkdown/CMakeLists.txt b/Userland/Libraries/LibMarkdown/CMakeLists.txt deleted file mode 100644 index ef316fbdac..0000000000 --- a/Userland/Libraries/LibMarkdown/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -set(SOURCES - BlockQuote.cpp - CodeBlock.cpp - CommentBlock.cpp - ContainerBlock.cpp - Document.cpp - Heading.cpp - HorizontalRule.cpp - LineIterator.cpp - List.cpp - Paragraph.cpp - SyntaxHighlighter.cpp - Table.cpp - Text.cpp -) - -serenity_lib(LibMarkdown markdown) -target_link_libraries(LibMarkdown PRIVATE LibUnicode LibJS LibRegex LibSyntax) diff --git a/Userland/Libraries/LibMarkdown/CodeBlock.cpp b/Userland/Libraries/LibMarkdown/CodeBlock.cpp deleted file mode 100644 index 6f89739028..0000000000 --- a/Userland/Libraries/LibMarkdown/CodeBlock.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * Copyright (c) 2022, Peter Elliott - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include - -namespace Markdown { - -ByteString CodeBlock::render_to_html(bool) const -{ - StringBuilder builder; - - builder.append("
"sv);
-
-    if (m_style.length() >= 2)
-        builder.append(""sv);
-    else if (m_style.length() >= 2)
-        builder.append(""sv);
-
-    if (m_language.is_empty())
-        builder.append(""sv);
-    else
-        builder.appendff("", 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(""sv);
-
-    if (m_style.length() >= 2)
-        builder.append(""sv);
-    else if (m_style.length() >= 2)
-        builder.append(""sv);
-
-    builder.append("
\n"sv); - - return builder.to_byte_string(); -} - -Vector CodeBlock::render_lines_for_terminal(size_t) const -{ - Vector 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', SplitBehavior::KeepEmpty)) - lines.append(ByteString::formatted("{}{}", indentation, line)); - - 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 open_fence_re("^ {0,3}(([\\`\\~])\\2{2,})\\s*([\\*_]*)\\s*([^\\*_\\s]*).*$"); -static Regex close_fence_re("^ {0,3}(([\\`\\~])\\2{2,})\\s*$"); - -static Optional 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::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::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(language, style, builder.to_byte_string(), current_section); -} - -OwnPtr 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("", "", builder.to_byte_string(), nullptr); -} -} diff --git a/Userland/Libraries/LibMarkdown/CodeBlock.h b/Userland/Libraries/LibMarkdown/CodeBlock.h deleted file mode 100644 index 0667e80eae..0000000000 --- a/Userland/Libraries/LibMarkdown/CodeBlock.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * Copyright (c) 2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace Markdown { - -class CodeBlock final : public Block { -public: - CodeBlock(ByteString const& language, ByteString const& style, ByteString const& code, Heading* current_section) - : m_code(move(code)) - , m_language(language) - , m_style(style) - , m_current_section(current_section) - { - } - virtual ~CodeBlock() override = default; - - virtual ByteString render_to_html(bool tight = false) const override; - virtual Vector render_lines_for_terminal(size_t view_width = 0) const override; - virtual RecursionDecision walk(Visitor&) const override; - static OwnPtr parse(LineIterator& lines, Heading* current_section); - -private: - ByteString m_code; - ByteString m_language; - ByteString m_style; - Heading* m_current_section; - - static OwnPtr parse_backticks(LineIterator& lines, Heading* current_section); - static OwnPtr parse_indent(LineIterator& lines); -}; - -} diff --git a/Userland/Libraries/LibMarkdown/CommentBlock.cpp b/Userland/Libraries/LibMarkdown/CommentBlock.cpp deleted file mode 100644 index e7054fbead..0000000000 --- a/Userland/Libraries/LibMarkdown/CommentBlock.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2021, Ben Wiederhake - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include - -namespace Markdown { - -ByteString CommentBlock::render_to_html(bool) const -{ - StringBuilder builder; - - builder.append("\n"sv); - - return builder.to_byte_string(); -} - -Vector CommentBlock::render_lines_for_terminal(size_t) const -{ - return Vector {}; -} - -RecursionDecision CommentBlock::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - // Normalize return value. - return RecursionDecision::Continue; -} - -OwnPtr CommentBlock::parse(LineIterator& lines) -{ - if (lines.is_end()) - return {}; - - constexpr auto comment_start = ""sv; - - StringView line = *lines; - if (!line.starts_with(comment_start)) - return {}; - line = line.substring_view(comment_start.length()); - - StringBuilder builder; - - while (true) { - // Invariant: At the beginning of the loop, `line` is valid and should be added to the builder. - bool ends_here = line.ends_with(comment_end); - if (ends_here) - line = line.substring_view(0, line.length() - comment_end.length()); - builder.append(line); - if (!ends_here) - builder.append('\n'); - - ++lines; - if (lines.is_end() || ends_here) { - break; - } - line = *lines; - } - - return make(builder.to_byte_string()); -} - -} diff --git a/Userland/Libraries/LibMarkdown/CommentBlock.h b/Userland/Libraries/LibMarkdown/CommentBlock.h deleted file mode 100644 index 388d7eab63..0000000000 --- a/Userland/Libraries/LibMarkdown/CommentBlock.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021, Ben Wiederhake - * Copyright (c) 2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -namespace Markdown { - -class CommentBlock final : public Block { -public: - CommentBlock(ByteString const& comment) - : m_comment(comment) - { - } - virtual ~CommentBlock() override = default; - - virtual ByteString render_to_html(bool tight = false) const override; - virtual Vector render_lines_for_terminal(size_t view_width = 0) const override; - virtual RecursionDecision walk(Visitor&) const override; - static OwnPtr parse(LineIterator& lines); - -private: - ByteString m_comment; -}; - -} diff --git a/Userland/Libraries/LibMarkdown/ContainerBlock.cpp b/Userland/Libraries/LibMarkdown/ContainerBlock.cpp deleted file mode 100644 index 45ae36ef2b..0000000000 --- a/Userland/Libraries/LibMarkdown/ContainerBlock.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2021, Peter Elliott - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Markdown { - -ByteString ContainerBlock::render_to_html(bool tight) const -{ - StringBuilder builder; - - for (size_t i = 0; i + 1 < m_blocks.size(); ++i) { - auto s = m_blocks[i]->render_to_html(tight); - builder.append(s); - } - - // I don't like this edge case. - if (m_blocks.size() != 0) { - auto& block = m_blocks[m_blocks.size() - 1]; - auto s = block->render_to_html(tight); - if (tight && dynamic_cast(block.ptr())) { - builder.append(s.substring_view(0, s.length() - 1)); - } else { - builder.append(s); - } - } - - return builder.to_byte_string(); -} - -Vector ContainerBlock::render_lines_for_terminal(size_t view_width) const -{ - Vector lines; - - for (auto& block : m_blocks) { - for (auto& line : block->render_lines_for_terminal(view_width)) - lines.append(move(line)); - } - - return lines; -} - -RecursionDecision ContainerBlock::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - for (auto const& block : m_blocks) { - rd = block->walk(visitor); - if (rd == RecursionDecision::Break) - return rd; - } - - return RecursionDecision::Continue; -} - -template -static bool try_parse_block(LineIterator& lines, Vector>& blocks, Heading* current_section) -{ - OwnPtr block = CodeBlock::parse(lines, current_section); - if (!block) - return false; - blocks.append(block.release_nonnull()); - return true; -} - -template -static bool try_parse_block(LineIterator& lines, Vector>& blocks) -{ - OwnPtr block = BlockType::parse(lines); - if (!block) - return false; - blocks.append(block.release_nonnull()); - return true; -} - -OwnPtr ContainerBlock::parse(LineIterator& lines) -{ - Vector> blocks; - - StringBuilder paragraph_text; - Heading* current_section = nullptr; - - auto flush_paragraph = [&] { - if (paragraph_text.is_empty()) - return; - auto paragraph = make(Text::parse(paragraph_text.to_byte_string())); - blocks.append(move(paragraph)); - paragraph_text.clear(); - }; - - bool has_blank_lines = false; - bool has_trailing_blank_lines = false; - - while (true) { - if (lines.is_end()) - break; - - if ((*lines).is_whitespace()) { - has_trailing_blank_lines = true; - ++lines; - - flush_paragraph(); - continue; - } else { - has_blank_lines = has_blank_lines || has_trailing_blank_lines; - } - - bool heading = false; - if ((heading = try_parse_block(lines, blocks))) - current_section = dynamic_cast(blocks.last().ptr()); - - bool any = heading - || try_parse_block(lines, blocks) - || try_parse_block(lines, blocks) - || try_parse_block(lines, blocks) - // CodeBlock needs to know the current section's name for proper indentation - || try_parse_block(lines, blocks, current_section) - || try_parse_block(lines, blocks) - || try_parse_block
(lines, blocks); - - if (any) { - if (!paragraph_text.is_empty()) { - auto last_block = blocks.take_last(); - flush_paragraph(); - blocks.append(move(last_block)); - } - continue; - } - - if (!paragraph_text.is_empty()) - paragraph_text.append('\n'); - paragraph_text.append(*lines++); - } - - flush_paragraph(); - - return make(move(blocks), has_blank_lines, has_trailing_blank_lines); -} - -} diff --git a/Userland/Libraries/LibMarkdown/ContainerBlock.h b/Userland/Libraries/LibMarkdown/ContainerBlock.h deleted file mode 100644 index aaed1c0a50..0000000000 --- a/Userland/Libraries/LibMarkdown/ContainerBlock.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2021, Peter Elliott - * Copyright (c) 2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -namespace Markdown { - -class ContainerBlock final : public Block { -public: - ContainerBlock(Vector> blocks, bool has_blank_lines, bool has_trailing_blank_lines) - : m_blocks(move(blocks)) - , m_has_blank_lines(has_blank_lines) - , m_has_trailing_blank_lines(has_trailing_blank_lines) - { - } - - virtual ~ContainerBlock() override = default; - - virtual ByteString render_to_html(bool tight = false) const override; - virtual Vector render_lines_for_terminal(size_t view_width = 0) const override; - virtual RecursionDecision walk(Visitor&) const override; - - static OwnPtr parse(LineIterator& lines); - - bool has_blank_lines() const { return m_has_blank_lines; } - bool has_trailing_blank_lines() const { return m_has_trailing_blank_lines; } - - Vector> const& blocks() const { return m_blocks; } - -private: - Vector> m_blocks; - bool m_has_blank_lines; - bool m_has_trailing_blank_lines; -}; - -} diff --git a/Userland/Libraries/LibMarkdown/Document.cpp b/Userland/Libraries/LibMarkdown/Document.cpp deleted file mode 100644 index 3dfb21461a..0000000000 --- a/Userland/Libraries/LibMarkdown/Document.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * Copyright (c) 2021, Peter Elliott - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include - -namespace Markdown { - -ByteString Document::render_to_html(StringView extra_head_contents) const -{ - StringBuilder builder; - builder.append(R"~~~( - - - -)~~~"sv); - if (!extra_head_contents.is_empty()) - builder.append(extra_head_contents); - builder.append(R"~~~( - - -)~~~"sv); - - builder.append(render_to_inline_html()); - - builder.append(R"~~~( - -)~~~"sv); - - return builder.to_byte_string(); -} - -ByteString Document::render_to_inline_html() const -{ - return m_container->render_to_html(); -} - -ErrorOr Document::render_for_terminal(size_t view_width) const -{ - StringBuilder builder; - for (auto& line : m_container->render_lines_for_terminal(view_width)) { - TRY(builder.try_append(line)); - TRY(builder.try_append("\n"sv)); - } - - return builder.to_string(); -} - -RecursionDecision Document::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - return m_container->walk(visitor); -} - -OwnPtr Document::parse(StringView str) -{ - Vector const lines_vec = str.lines(); - LineIterator lines(lines_vec.begin()); - return make(ContainerBlock::parse(lines)); -} - -} diff --git a/Userland/Libraries/LibMarkdown/Document.h b/Userland/Libraries/LibMarkdown/Document.h deleted file mode 100644 index bdb211dfc5..0000000000 --- a/Userland/Libraries/LibMarkdown/Document.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace Markdown { - -class Document final { -public: - Document(OwnPtr container) - : m_container(move(container)) - { - } - ByteString render_to_html(StringView extra_head_contents = ""sv) const; - ByteString render_to_inline_html() const; - ErrorOr render_for_terminal(size_t view_width = 0) const; - - /* - * Walk recursively through the document tree. Returning `RecursionDecision::Recurse` from - * `Visitor::visit` proceeds with the next element of the pre-order walk, usually a child element. - * Returning `RecursionDecision::Continue` skips the subtree, and usually proceeds with the next - * sibling. Returning `RecursionDecision::Break` breaks the recursion, with no further calls to - * any of the `Visitor::visit` methods. - * - * Note that `walk()` will only return `RecursionDecision::Continue` or `RecursionDecision::Break`. - */ - RecursionDecision walk(Visitor&) const; - - static OwnPtr parse(StringView); - -private: - OwnPtr m_container; -}; - -} diff --git a/Userland/Libraries/LibMarkdown/Forward.h b/Userland/Libraries/LibMarkdown/Forward.h deleted file mode 100644 index 5261a41d95..0000000000 --- a/Userland/Libraries/LibMarkdown/Forward.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2021, Ben Wiederhake - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -namespace Markdown { - -class Block; -class Document; -class Text; - -class BlockQuote; -class CodeBlock; -class ContainerBlock; -class Heading; -class HorizontalRule; -class List; -class Paragraph; -class Table; - -class Visitor; - -} diff --git a/Userland/Libraries/LibMarkdown/Heading.cpp b/Userland/Libraries/LibMarkdown/Heading.cpp deleted file mode 100644 index 5d08e7d39e..0000000000 --- a/Userland/Libraries/LibMarkdown/Heading.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include - -namespace Markdown { - -ByteString Heading::render_to_html(bool) const -{ - auto input = Unicode::normalize(m_text.render_for_raw_print(), Unicode::NormalizationForm::NFD); - auto slugified = MUST(AK::slugify(input)); - return ByteString::formatted("# {}\n", m_level, slugified, slugified, m_text.render_to_html(), m_level); -} - -Vector Heading::render_lines_for_terminal(size_t) const -{ - StringBuilder builder; - - builder.append("\n\033[0;31;1m"sv); - switch (m_level) { - case 1: - case 2: - builder.append(m_text.render_for_terminal().to_uppercase()); - builder.append("\033[0m"sv); - break; - default: - builder.append(m_text.render_for_terminal()); - builder.append("\033[0m"sv); - break; - } - - return Vector { builder.to_byte_string() }; -} - -RecursionDecision Heading::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - return m_text.walk(visitor); -} - -OwnPtr Heading::parse(LineIterator& lines) -{ - if (lines.is_end()) - return {}; - - StringView line = *lines; - size_t indent = 0; - - // Allow for up to 3 spaces of indentation. - // https://spec.commonmark.org/0.30/#example-68 - for (size_t i = 0; i < 3; ++i) { - if (line[i] != ' ') - break; - - ++indent; - } - - size_t level; - - for (level = 0; indent + level < line.length(); level++) { - if (line[indent + level] != '#') - break; - } - - if (!level || indent + level >= line.length() || line[indent + level] != ' ' || level > 6) - return {}; - - StringView title_view = line.substring_view(indent + level + 1); - auto text = Text::parse(title_view); - auto heading = make(move(text), level); - - ++lines; - return heading; -} - -} diff --git a/Userland/Libraries/LibMarkdown/Heading.h b/Userland/Libraries/LibMarkdown/Heading.h deleted file mode 100644 index 04247a9774..0000000000 --- a/Userland/Libraries/LibMarkdown/Heading.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * Copyright (c) 2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace Markdown { - -class Heading final : public Block { -public: - Heading(Text&& text, size_t level) - : m_text(move(text)) - , m_level(level) - { - VERIFY(m_level > 0); - } - virtual ~Heading() override = default; - - virtual ByteString render_to_html(bool tight = false) const override; - virtual Vector render_lines_for_terminal(size_t view_width = 0) const override; - virtual RecursionDecision walk(Visitor&) const override; - static OwnPtr parse(LineIterator& lines); - -private: - Text m_text; - size_t m_level { 0 }; -}; - -} diff --git a/Userland/Libraries/LibMarkdown/HorizontalRule.cpp b/Userland/Libraries/LibMarkdown/HorizontalRule.cpp deleted file mode 100644 index 416b2e28ff..0000000000 --- a/Userland/Libraries/LibMarkdown/HorizontalRule.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2021, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include - -namespace Markdown { - -ByteString HorizontalRule::render_to_html(bool) const -{ - return "
\n"; -} - -Vector HorizontalRule::render_lines_for_terminal(size_t view_width) const -{ - StringBuilder builder(view_width + 1); - for (size_t i = 0; i < view_width; ++i) - builder.append('-'); - builder.append("\n\n"sv); - return Vector { builder.to_byte_string() }; -} - -RecursionDecision HorizontalRule::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - // Normalize return value. - return RecursionDecision::Continue; -} - -static Regex thematic_break_re("^ {0,3}([\\*\\-_])\\s*(\\1\\s*){2,}$"); - -OwnPtr HorizontalRule::parse(LineIterator& lines) -{ - if (lines.is_end()) - return {}; - - StringView line = *lines; - - auto match = thematic_break_re.match(line); - if (!match.success) - return {}; - - ++lines; - return make(); -} - -} diff --git a/Userland/Libraries/LibMarkdown/HorizontalRule.h b/Userland/Libraries/LibMarkdown/HorizontalRule.h deleted file mode 100644 index bdbc206d57..0000000000 --- a/Userland/Libraries/LibMarkdown/HorizontalRule.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2021, Andreas Kling - * Copyright (c) 2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace Markdown { - -class HorizontalRule final : public Block { -public: - HorizontalRule() = default; - virtual ~HorizontalRule() override = default; - - virtual ByteString render_to_html(bool tight = false) const override; - virtual Vector render_lines_for_terminal(size_t view_width = 0) const override; - virtual RecursionDecision walk(Visitor&) const override; - static OwnPtr parse(LineIterator& lines); -}; - -} diff --git a/Userland/Libraries/LibMarkdown/LineIterator.cpp b/Userland/Libraries/LibMarkdown/LineIterator.cpp deleted file mode 100644 index a471007474..0000000000 --- a/Userland/Libraries/LibMarkdown/LineIterator.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2021, Peter Elliott - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include - -namespace Markdown { - -void LineIterator::reset_ignore_prefix() -{ - for (auto& context : m_context_stack) { - context.ignore_prefix = false; - } -} - -Optional LineIterator::match_context(StringView line) const -{ - bool is_ws = line.is_whitespace(); - size_t offset = 0; - for (auto& context : m_context_stack) { - switch (context.type) { - case Context::Type::ListItem: - if (is_ws) - break; - - if (offset + context.indent > line.length()) - return {}; - - if (!context.ignore_prefix && !line.substring_view(offset, context.indent).is_whitespace()) - return {}; - - offset += context.indent; - - break; - case Context::Type::BlockQuote: - for (; offset < line.length() && line[offset] == ' '; ++offset) { } - if (offset >= line.length() || line[offset] != '>') { - return {}; - } - ++offset; - break; - } - - if (offset > line.length()) - return {}; - } - return line.substring_view(offset); -} - -bool LineIterator::is_end() const -{ - return m_iterator.is_end() || !match_context(*m_iterator).has_value(); -} - -StringView LineIterator::operator*() const -{ - auto line = match_context(*m_iterator); - VERIFY(line.has_value()); - return line.value(); -} - -} diff --git a/Userland/Libraries/LibMarkdown/LineIterator.h b/Userland/Libraries/LibMarkdown/LineIterator.h deleted file mode 100644 index cc76ef04b7..0000000000 --- a/Userland/Libraries/LibMarkdown/LineIterator.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2021, Peter Elliott - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include - -namespace Markdown { - -template -class FakePtr { -public: - FakePtr(T item) - : m_item(move(item)) - { - } - - T const* operator->() const { return &m_item; } - T* operator->() { return &m_item; } - -private: - T m_item; -}; - -class LineIterator { -public: - struct Context { - enum class Type { - ListItem, - BlockQuote, - }; - - Type type; - size_t indent; - bool ignore_prefix; - - static Context list_item(size_t indent) { return { Type::ListItem, indent, true }; } - static Context block_quote() { return { Type::BlockQuote, 0, false }; } - }; - - LineIterator(Vector::ConstIterator const& lines) - : m_iterator(lines) - { - } - - bool is_end() const; - StringView operator*() const; - - LineIterator operator++() - { - reset_ignore_prefix(); - ++m_iterator; - return *this; - } - - LineIterator operator++(int) - { - LineIterator tmp = *this; - reset_ignore_prefix(); - ++m_iterator; - return tmp; - } - - LineIterator operator+(ptrdiff_t delta) const - { - LineIterator copy = *this; - copy.reset_ignore_prefix(); - copy.m_iterator = copy.m_iterator + delta; - return copy; - } - - LineIterator operator-(ptrdiff_t delta) const - { - LineIterator copy = *this; - copy.reset_ignore_prefix(); - copy.m_iterator = copy.m_iterator - delta; - return copy; - } - - ptrdiff_t operator-(LineIterator const& other) const { return m_iterator - other.m_iterator; } - - FakePtr operator->() const { return FakePtr(operator*()); } - - void push_context(Context context) { m_context_stack.append(move(context)); } - void pop_context() { m_context_stack.take_last(); } - -private: - void reset_ignore_prefix(); - Optional match_context(StringView line) const; - - Vector::ConstIterator m_iterator; - Vector m_context_stack; -}; - -} diff --git a/Userland/Libraries/LibMarkdown/List.cpp b/Userland/Libraries/LibMarkdown/List.cpp deleted file mode 100644 index c953177892..0000000000 --- a/Userland/Libraries/LibMarkdown/List.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * Copyright (c) 2021, Peter Elliott - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include - -namespace Markdown { - -ByteString List::render_to_html(bool) const -{ - StringBuilder builder; - - char const* tag = m_is_ordered ? "ol" : "ul"; - builder.appendff("<{}", tag); - - if (m_start_number != 1) - builder.appendff(" start=\"{}\"", m_start_number); - - builder.append(">\n"sv); - - for (auto& item : m_items) { - builder.append("
  • "sv); - if (!m_is_tight || (item->blocks().size() != 0 && !dynamic_cast(item->blocks()[0].ptr()))) - builder.append('\n'); - builder.append(item->render_to_html(m_is_tight)); - builder.append("
  • \n"sv); - } - - builder.appendff("\n", tag); - - return builder.to_byte_string(); -} - -Vector List::render_lines_for_terminal(size_t view_width) const -{ - Vector lines; - - int i = 0; - for (auto& item : m_items) { - auto item_lines = item->render_lines_for_terminal(view_width); - auto first_line = item_lines.take_first(); - - StringBuilder builder; - builder.append(" "sv); - if (m_is_ordered) - builder.appendff("{}.", ++i); - else - builder.append('*'); - auto item_indentation = builder.length(); - - builder.append(first_line); - - lines.append(builder.to_byte_string()); - - for (auto& line : item_lines) { - builder.clear(); - builder.append(ByteString::repeated(' ', item_indentation)); - builder.append(line); - lines.append(builder.to_byte_string()); - } - } - - return lines; -} - -RecursionDecision List::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - for (auto const& block : m_items) { - rd = block->walk(visitor); - if (rd == RecursionDecision::Break) - return rd; - } - - return RecursionDecision::Continue; -} - -OwnPtr List::parse(LineIterator& lines) -{ - Vector> items; - - bool first = true; - bool is_ordered = false; - - bool is_tight = true; - bool has_trailing_blank_lines = false; - size_t start_number = 1; - - while (!lines.is_end()) { - - size_t offset = 0; - - StringView line = *lines; - - bool appears_unordered = false; - - while (offset < line.length() && line[offset] == ' ') - ++offset; - - if (offset + 2 <= line.length()) { - if (line[offset + 1] == ' ' && (line[offset] == '*' || line[offset] == '-' || line[offset] == '+')) { - appears_unordered = true; - offset++; - } - } - - bool appears_ordered = false; - for (size_t i = offset; i < 10 && i < line.length(); i++) { - char ch = line[i]; - if ('0' <= ch && ch <= '9') - continue; - if (ch == '.' || ch == ')') - if (i + 1 < line.length() && line[i + 1] == ' ') { - auto maybe_start_number = line.substring_view(offset, i - offset).to_number(); - if (!maybe_start_number.has_value()) - break; - if (first) - start_number = maybe_start_number.value(); - appears_ordered = true; - offset = i + 1; - } - break; - } - - VERIFY(!(appears_unordered && appears_ordered)); - if (!appears_unordered && !appears_ordered) { - if (first) - return {}; - - break; - } - - while (offset < line.length() && line[offset] == ' ') - offset++; - - if (first) { - is_ordered = appears_ordered; - } else if (appears_ordered != is_ordered) { - break; - } - - is_tight = is_tight && !has_trailing_blank_lines; - - lines.push_context(LineIterator::Context::list_item(offset)); - - auto list_item = ContainerBlock::parse(lines); - is_tight = is_tight && !list_item->has_blank_lines(); - has_trailing_blank_lines = has_trailing_blank_lines || list_item->has_trailing_blank_lines(); - items.append(move(list_item)); - - lines.pop_context(); - - first = false; - } - - return make(move(items), is_ordered, is_tight, start_number); -} - -} diff --git a/Userland/Libraries/LibMarkdown/List.h b/Userland/Libraries/LibMarkdown/List.h deleted file mode 100644 index 1ac888c584..0000000000 --- a/Userland/Libraries/LibMarkdown/List.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * Copyright (c) 2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -namespace Markdown { - -class List final : public Block { -public: - List(Vector> items, bool is_ordered, bool is_tight, size_t start_number) - : m_items(move(items)) - , m_is_ordered(is_ordered) - , m_is_tight(is_tight) - , m_start_number(start_number) - { - } - virtual ~List() override = default; - - virtual ByteString render_to_html(bool tight = false) const override; - virtual Vector render_lines_for_terminal(size_t view_width = 0) const override; - virtual RecursionDecision walk(Visitor&) const override; - - static OwnPtr parse(LineIterator& lines); - -private: - Vector> m_items; - bool m_is_ordered { false }; - bool m_is_tight { false }; - size_t m_start_number { 1 }; -}; - -} diff --git a/Userland/Libraries/LibMarkdown/Paragraph.cpp b/Userland/Libraries/LibMarkdown/Paragraph.cpp deleted file mode 100644 index 4110d26c59..0000000000 --- a/Userland/Libraries/LibMarkdown/Paragraph.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include - -namespace Markdown { - -ByteString Paragraph::render_to_html(bool tight) const -{ - StringBuilder builder; - - if (!tight) - builder.append("

    "sv); - - builder.append(m_text.render_to_html()); - - if (!tight) - builder.append("

    "sv); - - builder.append('\n'); - - return builder.to_byte_string(); -} - -Vector Paragraph::render_lines_for_terminal(size_t) const -{ - return Vector { ByteString::formatted(" {}", m_text.render_for_terminal()), "" }; -} - -RecursionDecision Paragraph::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - return m_text.walk(visitor); -} - -} diff --git a/Userland/Libraries/LibMarkdown/Paragraph.h b/Userland/Libraries/LibMarkdown/Paragraph.h deleted file mode 100644 index ed2599b5bc..0000000000 --- a/Userland/Libraries/LibMarkdown/Paragraph.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * Copyright (c) 2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include - -namespace Markdown { - -class Paragraph final : public Block { -public: - Paragraph(Text text) - : m_text(move(text)) - { - } - - virtual ~Paragraph() override = default; - - virtual ByteString render_to_html(bool tight = false) const override; - virtual Vector render_lines_for_terminal(size_t view_width = 0) const override; - virtual RecursionDecision walk(Visitor&) const override; - -private: - Text m_text; -}; - -} diff --git a/Userland/Libraries/LibMarkdown/SyntaxHighlighter.cpp b/Userland/Libraries/LibMarkdown/SyntaxHighlighter.cpp deleted file mode 100644 index 16380e8ba0..0000000000 --- a/Userland/Libraries/LibMarkdown/SyntaxHighlighter.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2023, Maciej - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include - -namespace Markdown { - -Syntax::Language SyntaxHighlighter::language() const -{ - return Syntax::Language::Markdown; -} - -Optional SyntaxHighlighter::comment_prefix() const -{ - return {}; -} - -Optional SyntaxHighlighter::comment_suffix() const -{ - return {}; -} - -enum class Token { - Default, - Header, - Code -}; - -void SyntaxHighlighter::rehighlight(Palette const& palette) -{ - auto text = m_client->get_text(); - - Vector spans; - - auto append_header = [&](Syntax::TextRange const& range) { - Gfx::TextAttributes attributes; - attributes.color = palette.base_text(); - attributes.bold = true; - Syntax::TextDocumentSpan span { - .range = range, - .attributes = attributes, - .data = static_cast(Token::Header), - .is_skippable = false - }; - spans.append(span); - }; - - auto append_code_block = [&](Syntax::TextRange const& range) { - Gfx::TextAttributes attributes; - attributes.color = palette.syntax_string(); - Syntax::TextDocumentSpan span { - .range = range, - .attributes = attributes, - .data = static_cast(Token::Code), - .is_skippable = false - }; - spans.append(span); - }; - - // Headers, code blocks - { - size_t line_index = 0; - Optional code_block_start; - for (auto const& line : StringView(text).lines()) { - if (line.starts_with("```"sv)) { - if (code_block_start.has_value()) { - append_code_block({ { *code_block_start, 0 }, { line_index, line.length() } }); - code_block_start = {}; - } else { - code_block_start = line_index; - } - } - - if (!code_block_start.has_value()) { - auto trimmed = line.trim_whitespace(TrimMode::Left); - size_t indent = line.length() - trimmed.length(); - if (indent < 4 && trimmed.starts_with("#"sv)) { - append_header({ { line_index, 0 }, { line_index, line.length() } }); - } - } - line_index++; - } - } - - // TODO: Highlight text nodes (em, strong, link, image) - - m_client->do_set_spans(spans); -} - -Vector SyntaxHighlighter::matching_token_pairs_impl() const -{ - return {}; -} - -bool SyntaxHighlighter::token_types_equal(u64 lhs, u64 rhs) const -{ - return static_cast(lhs) == static_cast(rhs); -} - -} diff --git a/Userland/Libraries/LibMarkdown/SyntaxHighlighter.h b/Userland/Libraries/LibMarkdown/SyntaxHighlighter.h deleted file mode 100644 index 0ea40226d9..0000000000 --- a/Userland/Libraries/LibMarkdown/SyntaxHighlighter.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2023, Maciej - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include - -namespace Markdown { - -class SyntaxHighlighter : public Syntax::Highlighter { - - virtual Syntax::Language language() const override; - virtual Optional comment_prefix() const override; - virtual Optional comment_suffix() const override; - virtual void rehighlight(Palette const&) override; - virtual Vector matching_token_pairs_impl() const override; - virtual bool token_types_equal(u64, u64) const override; -}; - -} diff --git a/Userland/Libraries/LibMarkdown/Table.cpp b/Userland/Libraries/LibMarkdown/Table.cpp deleted file mode 100644 index d65dfdf69e..0000000000 --- a/Userland/Libraries/LibMarkdown/Table.cpp +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include - -namespace Markdown { - -Vector Table::render_lines_for_terminal(size_t view_width) const -{ - auto unit_width_length = view_width == 0 ? 4 : ((float)(view_width - m_columns.size()) / (float)m_total_width); - StringBuilder builder; - Vector lines; - - auto write_aligned = [&](auto const& text, auto width, auto alignment) { - size_t original_length = text.terminal_length(); - auto string = text.render_for_terminal(); - if (alignment == Alignment::Center) { - auto padding_length = (width - original_length) / 2; - // FIXME: We're using a StringView literal to bypass the compile-time AK::Format checking here, since it can't handle the "}}" - builder.appendff("{:{1}}"sv, "", (int)padding_length); - builder.append(string); - builder.appendff("{:{1}}"sv, "", (int)padding_length); - if ((width - original_length) % 2) - builder.append(' '); - } else { - // FIXME: We're using StringView literals to bypass the compile-time AK::Format checking here, since it can't handle the "}}" - builder.appendff(alignment == Alignment::Left ? "{:<{1}}"sv : "{:>{1}}"sv, string, (int)(width + (string.length() - original_length))); - } - }; - - bool first = true; - for (auto& col : m_columns) { - if (!first) - builder.append('|'); - first = false; - size_t width = col.relative_width * unit_width_length; - write_aligned(col.header, width, col.alignment); - } - - lines.append(builder.to_byte_string()); - builder.clear(); - - for (size_t i = 0; i < view_width; ++i) - builder.append('-'); - lines.append(builder.to_byte_string()); - builder.clear(); - - for (size_t i = 0; i < m_row_count; ++i) { - bool first = true; - for (auto& col : m_columns) { - VERIFY(i < col.rows.size()); - auto& cell = col.rows[i]; - - if (!first) - builder.append('|'); - first = false; - - size_t width = col.relative_width * unit_width_length; - write_aligned(cell, width, col.alignment); - } - lines.append(builder.to_byte_string()); - builder.clear(); - } - - lines.append(""); - - return lines; -} - -ByteString Table::render_to_html(bool) const -{ - auto alignment_string = [](Alignment alignment) { - switch (alignment) { - case Alignment::Center: - return "center"sv; - case Alignment::Left: - return "left"sv; - case Alignment::Right: - return "right"sv; - } - VERIFY_NOT_REACHED(); - }; - - StringBuilder builder; - - builder.append("
    "sv); - builder.append(""sv); - builder.append(""sv); - for (auto& column : m_columns) { - builder.appendff(""sv); - } - builder.append(""sv); - builder.append(""sv); - builder.append(""sv); - for (size_t i = 0; i < m_row_count; ++i) { - builder.append(""sv); - for (auto& column : m_columns) { - VERIFY(i < column.rows.size()); - builder.appendff(""sv); - } - builder.append(""sv); - } - builder.append(""sv); - builder.append("
    ", alignment_string(column.alignment)); - builder.append(column.header.render_to_html()); - builder.append("
    ", alignment_string(column.alignment)); - builder.append(column.rows[i].render_to_html()); - builder.append("
    "sv); - - return builder.to_byte_string(); -} - -RecursionDecision Table::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - for (auto const& column : m_columns) { - rd = column.walk(visitor); - if (rd == RecursionDecision::Break) - return rd; - } - - return RecursionDecision::Continue; -} - -OwnPtr Table::parse(LineIterator& lines) -{ - auto peek_it = lines; - auto first_line = *peek_it; - if (!first_line.starts_with('|')) - return {}; - - ++peek_it; - - if (peek_it.is_end()) - return {}; - - auto header_segments = first_line.split_view('|', SplitBehavior::KeepEmpty); - auto header_delimiters = peek_it->split_view('|', SplitBehavior::KeepEmpty); - - if (!header_segments.is_empty()) - header_segments.take_first(); - if (!header_segments.is_empty() && header_segments.last().is_empty()) - header_segments.take_last(); - - if (!header_delimiters.is_empty()) - header_delimiters.take_first(); - if (!header_delimiters.is_empty() && header_delimiters.last().is_empty()) - header_delimiters.take_last(); - - ++peek_it; - - if (header_delimiters.size() != header_segments.size()) - return {}; - - if (header_delimiters.is_empty()) - return {}; - - size_t total_width = 0; - - auto table = make
    (); - table->m_columns.resize(header_delimiters.size()); - - for (size_t i = 0; i < header_segments.size(); ++i) { - auto text = Text::parse(header_segments[i]); - - auto& column = table->m_columns[i]; - - column.header = move(text); - - auto delimiter = header_delimiters[i].trim_whitespace(); - - auto align_left = delimiter.starts_with(':'); - auto align_right = delimiter != ":" && delimiter.ends_with(':'); - - if (align_left) - delimiter = delimiter.substring_view(1, delimiter.length() - 1); - if (align_right) - delimiter = delimiter.substring_view(0, delimiter.length() - 1); - - if (align_left && align_right) - column.alignment = Alignment::Center; - else if (align_right) - column.alignment = Alignment::Right; - else - column.alignment = Alignment::Left; - - size_t relative_width = delimiter.length(); - for (auto ch : delimiter) { - if (ch != '-') { - dbgln_if(MARKDOWN_DEBUG, "Invalid character _{}_ in table heading delimiter (ignored)", ch); - --relative_width; - } - } - - column.relative_width = relative_width; - total_width += relative_width; - } - - table->m_total_width = total_width; - - for (off_t i = 0; i < peek_it - lines; ++i) - ++lines; - - size_t row_count = 0; - ++lines; - while (!lines.is_end()) { - auto line = *lines; - if (!line.starts_with('|')) - break; - - ++lines; - - auto segments = line.split_view('|', SplitBehavior::KeepEmpty); - segments.take_first(); - if (!segments.is_empty() && segments.last().is_empty()) - segments.take_last(); - ++row_count; - - for (size_t i = 0; i < header_segments.size(); ++i) { - if (i >= segments.size()) { - // Ran out of segments, but still have headers. - // Just make an empty cell. - table->m_columns[i].rows.append(Text::parse(""sv)); - } else { - auto text = Text::parse(segments[i]); - table->m_columns[i].rows.append(move(text)); - } - } - } - - table->m_row_count = row_count; - - return table; -} - -RecursionDecision Table::Column::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - rd = header.walk(visitor); - if (rd != RecursionDecision::Recurse) - return rd; - - for (auto const& row : rows) { - rd = row.walk(visitor); - if (rd == RecursionDecision::Break) - return rd; - } - - return RecursionDecision::Continue; -} - -} diff --git a/Userland/Libraries/LibMarkdown/Table.h b/Userland/Libraries/LibMarkdown/Table.h deleted file mode 100644 index a147ad015d..0000000000 --- a/Userland/Libraries/LibMarkdown/Table.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2020-2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -namespace Markdown { - -class Table final : public Block { -public: - enum class Alignment { - Center, - Left, - Right, - }; - - struct Column { - Text header; - Vector rows; - Alignment alignment { Alignment::Left }; - size_t relative_width { 0 }; - - RecursionDecision walk(Visitor&) const; - }; - - Table() = default; - virtual ~Table() override = default; - - virtual ByteString render_to_html(bool tight = false) const override; - virtual Vector render_lines_for_terminal(size_t view_width = 0) const override; - virtual RecursionDecision walk(Visitor&) const override; - static OwnPtr
    parse(LineIterator& lines); - - Vector const& columns() const { return m_columns; } - -private: - Vector m_columns; - size_t m_total_width { 1 }; - size_t m_row_count { 0 }; -}; - -} diff --git a/Userland/Libraries/LibMarkdown/Text.cpp b/Userland/Libraries/LibMarkdown/Text.cpp deleted file mode 100644 index 4d9cc6c3a9..0000000000 --- a/Userland/Libraries/LibMarkdown/Text.cpp +++ /dev/null @@ -1,724 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * Copyright (c) 2021, Peter Elliott - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include - -namespace Markdown { - -void Text::EmphasisNode::render_to_html(StringBuilder& builder) const -{ - builder.append((strong) ? ""sv : ""sv); - child->render_to_html(builder); - builder.append((strong) ? ""sv : ""sv); -} - -void Text::EmphasisNode::render_for_terminal(StringBuilder& builder) const -{ - if (strong) { - builder.append("\e[1m"sv); - child->render_for_terminal(builder); - builder.append("\e[22m"sv); - } else { - builder.append("\e[3m"sv); - child->render_for_terminal(builder); - builder.append("\e[23m"sv); - } -} - -void Text::EmphasisNode::render_for_raw_print(StringBuilder& builder) const -{ - child->render_for_raw_print(builder); -} - -size_t Text::EmphasisNode::terminal_length() const -{ - return child->terminal_length(); -} - -RecursionDecision Text::EmphasisNode::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - return child->walk(visitor); -} - -void Text::CodeNode::render_to_html(StringBuilder& builder) const -{ - builder.append(""sv); - code->render_to_html(builder); - builder.append(""sv); -} - -void Text::CodeNode::render_for_terminal(StringBuilder& builder) const -{ - builder.append("\e[1m"sv); - code->render_for_terminal(builder); - builder.append("\e[22m"sv); -} - -void Text::CodeNode::render_for_raw_print(StringBuilder& builder) const -{ - code->render_for_raw_print(builder); -} - -size_t Text::CodeNode::terminal_length() const -{ - return code->terminal_length(); -} - -RecursionDecision Text::CodeNode::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - return code->walk(visitor); -} - -void Text::BreakNode::render_to_html(StringBuilder& builder) const -{ - builder.append("
    "sv); -} - -void Text::BreakNode::render_for_terminal(StringBuilder&) const -{ -} - -void Text::BreakNode::render_for_raw_print(StringBuilder&) const -{ -} - -size_t Text::BreakNode::terminal_length() const -{ - return 0; -} - -RecursionDecision Text::BreakNode::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - // Normalize return value - return RecursionDecision::Continue; -} - -void Text::TextNode::render_to_html(StringBuilder& builder) const -{ - builder.append(escape_html_entities(text)); -} - -void Text::TextNode::render_for_raw_print(StringBuilder& builder) const -{ - builder.append(text); -} - -void Text::TextNode::render_for_terminal(StringBuilder& builder) const -{ - if (collapsible && (text == "\n" || text.is_whitespace())) { - builder.append(' '); - } else { - builder.append(text); - } -} - -size_t Text::TextNode::terminal_length() const -{ - if (collapsible && text.is_whitespace()) { - return 1; - } - - return text.length(); -} - -RecursionDecision Text::TextNode::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - rd = visitor.visit(text); - if (rd != RecursionDecision::Recurse) - return rd; - // Normalize return value - return RecursionDecision::Continue; -} - -void Text::LinkNode::render_to_html(StringBuilder& builder) const -{ - if (is_image) { - builder.append("\""sv);render_to_html(builder); - builder.append("\" >"sv); - } else { - builder.append(""sv); - text->render_to_html(builder); - builder.append(""sv); - } -} - -void Text::LinkNode::render_for_raw_print(StringBuilder& builder) const -{ - text->render_for_raw_print(builder); -} - -void Text::LinkNode::render_for_terminal(StringBuilder& builder) const -{ - bool is_linked = href.contains("://"sv); - if (is_linked) { - builder.append("\033[0;34m\e]8;;"sv); - builder.append(href); - builder.append("\e\\"sv); - } - - text->render_for_terminal(builder); - - if (is_linked) { - builder.appendff(" <{}>", href); - builder.append("\033]8;;\033\\\033[0m"sv); - } -} - -size_t Text::LinkNode::terminal_length() const -{ - return text->terminal_length(); -} - -RecursionDecision Text::LinkNode::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - // Don't recurse on href. - - return text->walk(visitor); -} - -void Text::MultiNode::render_to_html(StringBuilder& builder) const -{ - for (auto& child : children) { - child->render_to_html(builder); - } -} - -void Text::MultiNode::render_for_raw_print(StringBuilder& builder) const -{ - for (auto& child : children) { - child->render_for_raw_print(builder); - } -} - -void Text::MultiNode::render_for_terminal(StringBuilder& builder) const -{ - for (auto& child : children) { - child->render_for_terminal(builder); - } -} - -size_t Text::MultiNode::terminal_length() const -{ - size_t length = 0; - for (auto& child : children) { - length += child->terminal_length(); - } - return length; -} - -RecursionDecision Text::MultiNode::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - for (auto const& child : children) { - rd = child->walk(visitor); - if (rd == RecursionDecision::Break) - return rd; - } - - return RecursionDecision::Continue; -} - -void Text::StrikeThroughNode::render_to_html(StringBuilder& builder) const -{ - builder.append(""sv); - striked_text->render_to_html(builder); - builder.append(""sv); -} - -void Text::StrikeThroughNode::render_for_raw_print(StringBuilder& builder) const -{ - striked_text->render_for_raw_print(builder); -} - -void Text::StrikeThroughNode::render_for_terminal(StringBuilder& builder) const -{ - builder.append("\e[9m"sv); - striked_text->render_for_terminal(builder); - builder.append("\e[29m"sv); -} - -size_t Text::StrikeThroughNode::terminal_length() const -{ - return striked_text->terminal_length(); -} - -RecursionDecision Text::StrikeThroughNode::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - return striked_text->walk(visitor); -} - -size_t Text::terminal_length() const -{ - return m_node->terminal_length(); -} - -ByteString Text::render_to_html() const -{ - StringBuilder builder; - m_node->render_to_html(builder); - return builder.to_byte_string().trim(" \n\t"sv); -} - -ByteString Text::render_for_raw_print() const -{ - StringBuilder builder; - m_node->render_for_raw_print(builder); - return builder.to_byte_string().trim(" \n\t"sv); -} - -ByteString Text::render_for_terminal() const -{ - StringBuilder builder; - m_node->render_for_terminal(builder); - return builder.to_byte_string().trim(" \n\t"sv); -} - -RecursionDecision Text::walk(Visitor& visitor) const -{ - RecursionDecision rd = visitor.visit(*this); - if (rd != RecursionDecision::Recurse) - return rd; - - return m_node->walk(visitor); -} - -Text Text::parse(StringView str) -{ - Text text; - auto const tokens = tokenize(str); - auto iterator = tokens.begin(); - text.m_node = parse_sequence(iterator, false); - return text; -} - -static bool flanking(StringView str, size_t start, size_t end, int dir) -{ - ssize_t next = ((dir > 0) ? end : start) + dir; - if (next < 0 || next >= (ssize_t)str.length()) - return false; - - if (isspace(str[next])) - return false; - - if (!ispunct(str[next])) - return true; - - ssize_t prev = ((dir > 0) ? start : end) - dir; - if (prev < 0 || prev >= (ssize_t)str.length()) - return true; - - return isspace(str[prev]) || ispunct(str[prev]); -} - -Vector Text::tokenize(StringView str) -{ - Vector tokens; - StringBuilder current_token; - - auto flush_run = [&](bool left_flanking, bool right_flanking, bool punct_before, bool punct_after, bool is_run) { - if (current_token.is_empty()) - return; - - tokens.append({ - current_token.to_byte_string(), - left_flanking, - right_flanking, - punct_before, - punct_after, - is_run, - }); - current_token.clear(); - }; - - auto flush_token = [&]() { - flush_run(false, false, false, false, false); - }; - - bool in_space = false; - - for (size_t offset = 0; offset < str.length(); ++offset) { - auto has = [&](StringView seq) { - if (offset + seq.length() > str.length()) - return false; - - return str.substring_view(offset, seq.length()) == seq; - }; - - auto expect = [&](StringView seq) { - VERIFY(has(seq)); - flush_token(); - current_token.append(seq); - flush_token(); - offset += seq.length() - 1; - }; - - char ch = str[offset]; - if (ch != ' ' && in_space) { - flush_token(); - in_space = false; - } - - if (ch == '\\' && offset + 1 < str.length() && ispunct(str[offset + 1])) { - current_token.append(str[offset + 1]); - ++offset; - } else if (ch == '*' || ch == '_' || ch == '`' || ch == '~') { - flush_token(); - - char delim = ch; - size_t run_offset; - for (run_offset = offset; run_offset < str.length() && str[run_offset] == delim; ++run_offset) { - current_token.append(str[run_offset]); - } - - flush_run(flanking(str, offset, run_offset - 1, +1), - flanking(str, offset, run_offset - 1, -1), - offset > 0 && ispunct(str[offset - 1]), - run_offset < str.length() && ispunct(str[run_offset]), - true); - offset = run_offset - 1; - - } else if (ch == ' ') { - if (!in_space) { - flush_token(); - in_space = true; - } - current_token.append(ch); - } else if (has("\n"sv)) { - expect("\n"sv); - } else if (has("["sv)) { - expect("["sv); - } else if (has("!["sv)) { - expect("!["sv); - } else if (has("]("sv)) { - expect("]("sv); - } else if (has(")"sv)) { - expect(")"sv); - } else { - current_token.append(ch); - } - } - flush_token(); - return tokens; -} - -NonnullOwnPtr Text::parse_sequence(Vector::ConstIterator& tokens, bool in_link) -{ - auto node = make(); - - for (; !tokens.is_end(); ++tokens) { - if (tokens->is_space()) { - node->children.append(parse_break(tokens)); - } else if (*tokens == "\n"sv) { - node->children.append(parse_newline(tokens)); - } else if (tokens->is_run) { - switch (tokens->run_char()) { - case '*': - case '_': - node->children.append(parse_emph(tokens, in_link)); - break; - case '`': - node->children.append(parse_code(tokens)); - break; - case '~': - node->children.append(parse_strike_through(tokens)); - break; - } - } else if (*tokens == "["sv || *tokens == "!["sv) { - node->children.append(parse_link(tokens)); - } else if (in_link && *tokens == "]("sv) { - return node; - } else { - node->children.append(make(tokens->data)); - } - - if (in_link && !tokens.is_end() && *tokens == "]("sv) - return node; - - if (tokens.is_end()) - break; - } - return node; -} - -NonnullOwnPtr Text::parse_break(Vector::ConstIterator& tokens) -{ - auto next_tok = tokens + 1; - if (next_tok.is_end() || *next_tok != "\n"sv) - return make(tokens->data); - - if (tokens->data.length() >= 2) - return make(); - - return make(); -} - -NonnullOwnPtr Text::parse_newline(Vector::ConstIterator& tokens) -{ - auto node = make(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.run_char() == '_' && opening.left_flanking && (!opening.right_flanking || opening.punct_before)); -} - -bool Text::can_close_for(Token const& opening, Text::Token const& closing) -{ - if (opening.run_char() != closing.run_char()) - return false; - - if (opening.run_length() != closing.run_length()) - return false; - - return (opening.run_char() == '~' && closing.right_flanking) || (opening.run_char() == '*' && closing.right_flanking) || (opening.run_char() == '_' && closing.right_flanking && (!closing.left_flanking || closing.punct_after)); -} - -NonnullOwnPtr Text::parse_emph(Vector::ConstIterator& tokens, bool in_link) -{ - auto opening = *tokens; - - // Check that the opening delimiter run is properly flanking. - if (!can_open(opening)) - return make(opening.data); - - auto child = make(); - for (++tokens; !tokens.is_end(); ++tokens) { - if (tokens->is_space()) { - child->children.append(parse_break(tokens)); - } else if (*tokens == "\n"sv) { - child->children.append(parse_newline(tokens)); - } else if (tokens->is_run) { - if (can_close_for(opening, *tokens)) { - return make(opening.run_length() >= 2, move(child)); - } - - switch (tokens->run_char()) { - case '*': - case '_': - child->children.append(parse_emph(tokens, in_link)); - break; - case '`': - child->children.append(parse_code(tokens)); - break; - case '~': - child->children.append(parse_strike_through(tokens)); - break; - } - } else if (*tokens == "["sv || *tokens == "!["sv) { - child->children.append(parse_link(tokens)); - } else if (in_link && *tokens == "]("sv) { - child->children.prepend(make(opening.data)); - return child; - } else { - child->children.append(make(tokens->data)); - } - - if (in_link && !tokens.is_end() && *tokens == "]("sv) { - child->children.prepend(make(opening.data)); - return child; - } - - if (tokens.is_end()) - break; - } - child->children.prepend(make(opening.data)); - return child; -} - -NonnullOwnPtr Text::parse_code(Vector::ConstIterator& tokens) -{ - auto opening = *tokens; - - auto is_closing = [&](Token const& token) { - return token.is_run && token.run_char() == '`' && token.run_length() == opening.run_length(); - }; - - bool is_all_whitespace = true; - auto code = make(); - for (auto iterator = tokens + 1; !iterator.is_end(); ++iterator) { - if (is_closing(*iterator)) { - tokens = iterator; - - // Strip first and last space, when appropriate. - if (!is_all_whitespace) { - auto& first = dynamic_cast(*code->children.first()); - auto& last = dynamic_cast(*code->children.last()); - if (first.text.starts_with(' ') && last.text.ends_with(' ')) { - first.text = first.text.substring(1); - last.text = last.text.substring(0, last.text.length() - 1); - } - } - - return make(move(code)); - } - - is_all_whitespace = is_all_whitespace && iterator->data.is_whitespace(); - code->children.append(make((*iterator == "\n"sv) ? " " : iterator->data, false)); - } - - return make(opening.data); -} - -NonnullOwnPtr Text::parse_link(Vector::ConstIterator& tokens) -{ - auto opening = *tokens++; - bool is_image = opening == "!["sv; - - auto link_text = parse_sequence(tokens, true); - - if (tokens.is_end() || *tokens != "]("sv) { - link_text->children.prepend(make(opening.data)); - return link_text; - } - auto separator = *tokens; - VERIFY(separator == "]("sv); - - Optional image_width; - Optional image_height; - - auto parse_image_dimensions = [&](StringView dimensions) -> bool { - if (!dimensions.starts_with('=')) - return false; - - ArmedScopeGuard clear_image_dimensions = [&] { - image_width = {}; - image_height = {}; - }; - - auto dimension_seperator = dimensions.find('x', 1); - if (!dimension_seperator.has_value()) - return false; - - auto width_string = dimensions.substring_view(1, *dimension_seperator - 1); - if (!width_string.is_empty()) { - auto width = width_string.to_number(); - if (!width.has_value()) - return false; - image_width = width; - } - - auto height_start = *dimension_seperator + 1; - if (height_start < dimensions.length()) { - auto height_string = dimensions.substring_view(height_start); - auto height = height_string.to_number(); - if (!height.has_value()) - return false; - image_height = height; - } - - clear_image_dimensions.disarm(); - return true; - }; - - StringBuilder address; - for (auto iterator = tokens + 1; !iterator.is_end(); ++iterator) { - // FIXME: What to do if there's multiple dimension tokens? - if (is_image && !address.is_empty() && parse_image_dimensions(iterator->data)) - continue; - - if (*iterator == ")"sv) { - tokens = iterator; - - ByteString href = address.to_byte_string().trim_whitespace(); - - // Add file:// if the link is an absolute path otherwise it will be assumed relative. - if (AK::StringUtils::starts_with(href, "/"sv, CaseSensitivity::CaseSensitive)) - href = ByteString::formatted("file://{}", href); - - return make(is_image, move(link_text), move(href), image_width, image_height); - } - - address.append(iterator->data); - } - - link_text->children.prepend(make(opening.data)); - link_text->children.append(make(separator.data)); - return link_text; -} - -NonnullOwnPtr Text::parse_strike_through(Vector::ConstIterator& tokens) -{ - auto opening = *tokens; - - auto is_closing = [&](Token const& token) { - return token.is_run && token.run_char() == '~' && token.run_length() == opening.run_length(); - }; - - bool is_all_whitespace = true; - auto striked_text = make(); - for (auto iterator = tokens + 1; !iterator.is_end(); ++iterator) { - if (is_closing(*iterator)) { - tokens = iterator; - - if (!is_all_whitespace) { - auto& first = dynamic_cast(*striked_text->children.first()); - auto& last = dynamic_cast(*striked_text->children.last()); - if (first.text.starts_with(' ') && last.text.ends_with(' ')) { - first.text = first.text.substring(1); - last.text = last.text.substring(0, last.text.length() - 1); - } - } - - return make(move(striked_text)); - } - - is_all_whitespace = is_all_whitespace && iterator->data.is_whitespace(); - striked_text->children.append(make((*iterator == "\n"sv) ? " " : iterator->data, false)); - } - - return make(opening.data); -} - -} diff --git a/Userland/Libraries/LibMarkdown/Text.h b/Userland/Libraries/LibMarkdown/Text.h deleted file mode 100644 index eeb719b5a7..0000000000 --- a/Userland/Libraries/LibMarkdown/Text.h +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * Copyright (c) 2021, Peter Elliott - * Copyright (c) 2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace Markdown { - -class Text final { -public: - class Node { - public: - virtual void render_to_html(StringBuilder& builder) const = 0; - virtual void render_for_terminal(StringBuilder& builder) const = 0; - virtual void render_for_raw_print(StringBuilder& builder) const = 0; - virtual size_t terminal_length() const = 0; - virtual RecursionDecision walk(Visitor&) const = 0; - - virtual ~Node() = default; - }; - - class EmphasisNode : public Node { - public: - bool strong; - NonnullOwnPtr child; - - EmphasisNode(bool strong, NonnullOwnPtr child) - : strong(strong) - , child(move(child)) - { - } - - virtual void render_to_html(StringBuilder& builder) const override; - virtual void render_for_terminal(StringBuilder& builder) const override; - virtual void render_for_raw_print(StringBuilder& builder) const override; - virtual size_t terminal_length() const override; - virtual RecursionDecision walk(Visitor&) const override; - }; - - class CodeNode : public Node { - public: - NonnullOwnPtr code; - - CodeNode(NonnullOwnPtr code) - : code(move(code)) - { - } - - virtual void render_to_html(StringBuilder& builder) const override; - virtual void render_for_terminal(StringBuilder& builder) const override; - virtual void render_for_raw_print(StringBuilder& builder) const override; - virtual size_t terminal_length() const override; - virtual RecursionDecision walk(Visitor&) 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 void render_for_raw_print(StringBuilder& builder) const override; - virtual size_t terminal_length() const override; - virtual RecursionDecision walk(Visitor&) const override; - }; - - class TextNode : public Node { - public: - ByteString text; - bool collapsible; - - TextNode(StringView text) - : text(text) - , collapsible(true) - { - } - - TextNode(StringView text, bool collapsible) - : text(text) - , collapsible(collapsible) - { - } - - virtual void render_to_html(StringBuilder& builder) const override; - virtual void render_for_terminal(StringBuilder& builder) const override; - virtual void render_for_raw_print(StringBuilder& builder) const override; - virtual size_t terminal_length() const override; - virtual RecursionDecision walk(Visitor&) const override; - }; - - class LinkNode : public Node { - public: - bool is_image; - NonnullOwnPtr text; - ByteString href; - Optional image_width; - Optional image_height; - - LinkNode(bool is_image, NonnullOwnPtr text, ByteString href, Optional image_width, Optional image_height) - : is_image(is_image) - , text(move(text)) - , href(move(href)) - , image_width(image_width) - , image_height(image_height) - { - } - - bool has_image_dimensions() const - { - return image_width.has_value() || image_height.has_value(); - } - virtual void render_to_html(StringBuilder& builder) const override; - virtual void render_for_terminal(StringBuilder& builder) const override; - virtual void render_for_raw_print(StringBuilder& builder) const override; - virtual size_t terminal_length() const override; - virtual RecursionDecision walk(Visitor&) const override; - }; - - class MultiNode : public Node { - public: - Vector> children; - - virtual void render_to_html(StringBuilder& builder) const override; - virtual void render_for_terminal(StringBuilder& builder) const override; - virtual void render_for_raw_print(StringBuilder& builder) const override; - virtual size_t terminal_length() const override; - virtual RecursionDecision walk(Visitor&) const override; - }; - - class StrikeThroughNode : public Node { - public: - NonnullOwnPtr striked_text; - - StrikeThroughNode(NonnullOwnPtr striked_text) - : striked_text(move(striked_text)) - { - } - - virtual void render_to_html(StringBuilder& builder) const override; - virtual void render_for_terminal(StringBuilder& builder) const override; - virtual void render_for_raw_print(StringBuilder& builder) const override; - virtual size_t terminal_length() const override; - virtual RecursionDecision walk(Visitor&) const override; - }; - - size_t terminal_length() const; - - ByteString render_to_html() const; - ByteString render_for_terminal() const; - ByteString render_for_raw_print() const; - RecursionDecision walk(Visitor&) const; - - static Text parse(StringView); - -private: - struct Token { - ByteString data; - // Flanking basically means that a delimiter run has a non-whitespace, - // non-punctuation character on the corresponding side. For a more exact - // definition, see the CommonMark spec. - bool left_flanking; - bool right_flanking; - bool punct_before; - bool punct_after; - // is_run indicates that this token is a 'delimiter run'. A delimiter - // run occurs when several of the same syntactical character ('`', '_', - // or '*') occur in a row. - bool is_run; - - char run_char() const - { - VERIFY(is_run); - return data[0]; - } - char run_length() const - { - VERIFY(is_run); - return data.length(); - } - bool is_space() const - { - return data[0] == ' '; - } - bool operator==(StringView str) const { return str == data; } - }; - - static Vector tokenize(StringView); - - static bool can_open(Token const& opening); - static bool can_close_for(Token const& opening, Token const& closing); - - static NonnullOwnPtr parse_sequence(Vector::ConstIterator& tokens, bool in_link); - static NonnullOwnPtr parse_break(Vector::ConstIterator& tokens); - static NonnullOwnPtr parse_newline(Vector::ConstIterator& tokens); - static NonnullOwnPtr parse_emph(Vector::ConstIterator& tokens, bool in_link); - static NonnullOwnPtr parse_code(Vector::ConstIterator& tokens); - static NonnullOwnPtr parse_link(Vector::ConstIterator& tokens); - static NonnullOwnPtr parse_strike_through(Vector::ConstIterator& tokens); - - OwnPtr m_node; -}; - -} diff --git a/Userland/Libraries/LibMarkdown/Visitor.h b/Userland/Libraries/LibMarkdown/Visitor.h deleted file mode 100644 index 14a33da9f4..0000000000 --- a/Userland/Libraries/LibMarkdown/Visitor.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2021, Ben Wiederhake - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Markdown { - -class Visitor { -public: - Visitor() = default; - virtual ~Visitor() = default; - - virtual RecursionDecision visit(Document const&) { return RecursionDecision::Recurse; } - - virtual RecursionDecision visit(BlockQuote const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(CodeBlock const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(CommentBlock const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(ContainerBlock const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(Heading const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(HorizontalRule const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(List const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(Paragraph const&) { return RecursionDecision::Recurse; } - - virtual RecursionDecision visit(Table const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(Table::Column const&) { return RecursionDecision::Recurse; } - - virtual RecursionDecision visit(Text const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(Text::BreakNode const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(Text::CodeNode const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(Text::EmphasisNode const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(Text::LinkNode const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(Text::MultiNode const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(Text::StrikeThroughNode const&) { return RecursionDecision::Recurse; } - virtual RecursionDecision visit(Text::TextNode const&) { return RecursionDecision::Recurse; } - - virtual RecursionDecision visit(ByteString const&) { return RecursionDecision::Recurse; } -}; - -} diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 0456826811..b832ff6d03 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -750,7 +750,7 @@ set(GENERATED_SOURCES serenity_lib(LibWeb web) -target_link_libraries(LibWeb PRIVATE LibCore LibCrypto LibJS LibMarkdown LibHTTP LibGemini LibGfx LibIPC LibLocale LibRegex LibSyntax LibTextCodec LibUnicode LibAudio LibVideo LibWasm LibXML LibIDL LibURL LibTLS) +target_link_libraries(LibWeb PRIVATE LibCore LibCrypto LibJS LibHTTP LibGemini LibGfx LibIPC LibLocale LibRegex LibSyntax LibTextCodec LibUnicode LibAudio LibVideo LibWasm LibXML LibIDL LibURL LibTLS) if (HAS_ACCELERATED_GRAPHICS) target_link_libraries(LibWeb PRIVATE ${ACCEL_GFX_LIBS}) diff --git a/Userland/Libraries/LibWeb/DOM/DocumentLoading.cpp b/Userland/Libraries/LibWeb/DOM/DocumentLoading.cpp index d0523bc036..a797cdf9b9 100644 --- a/Userland/Libraries/LibWeb/DOM/DocumentLoading.cpp +++ b/Userland/Libraries/LibWeb/DOM/DocumentLoading.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -37,74 +36,6 @@ static void convert_to_xml_error_document(DOM::Document& document, String error_ MUST(document.append_child(html_element)); } -static WebIDL::ExceptionOr> load_markdown_document(HTML::NavigationParams const& navigation_params) -{ - auto extra_head_contents = R"~~~( - - -)~~~"sv; - - return create_document_for_inline_content(navigation_params.navigable.ptr(), navigation_params.id, [&](DOM::Document& document) { - auto& realm = document.realm(); - auto process_body = JS::create_heap_function(realm.heap(), [&document, url = navigation_params.response->url().value(), extra_head_contents](ByteBuffer data) { - auto markdown_document = Markdown::Document::parse(data); - if (!markdown_document) - return; - - auto parser = HTML::HTMLParser::create(document, markdown_document->render_to_html(extra_head_contents), "utf-8"sv); - parser->run(url); - }); - - auto process_body_error = JS::create_heap_function(realm.heap(), [](JS::Value) { - dbgln("FIXME: Load html page with an error if read of body failed."); - }); - - navigation_params.response->body()->fully_read( - realm, - process_body, - process_body_error, - JS::NonnullGCPtr { realm.global_object() }); - }); -} - bool build_xml_document(DOM::Document& document, ByteBuffer const& data, Optional content_encoding) { Optional decoder; @@ -533,8 +464,6 @@ JS::GCPtr load_document(HTML::NavigationParams const& navigation_ // native rendering of the content or an error message because the specified type is not supported, then // return the result of creating a document for inline content that doesn't have a DOM given navigationParams's // navigable, navigationParams's id, and navigationParams's navigation timing type. - if (type.essence() == "text/markdown"sv) - return load_markdown_document(navigation_params).release_value_but_fixme_should_propagate_errors(); // FIXME: 4. Otherwise, the document's type is such that the resource will not affect navigationParams's navigable, // e.g., because the resource is to be handed to an external application or because it is an unknown type