CodeBlock.cpp 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /*
  2. * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
  3. * Copyright (c) 2022, Peter Elliott <pelliott@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/StringBuilder.h>
  8. #include <LibJS/MarkupGenerator.h>
  9. #include <LibMarkdown/CodeBlock.h>
  10. #include <LibMarkdown/Visitor.h>
  11. #include <LibRegex/Regex.h>
  12. namespace Markdown {
  13. String CodeBlock::render_to_html(bool) const
  14. {
  15. StringBuilder builder;
  16. builder.append("<pre>");
  17. if (m_style.length() >= 2)
  18. builder.append("<strong>");
  19. else if (m_style.length() >= 2)
  20. builder.append("<em>");
  21. if (m_language.is_empty())
  22. builder.append("<code>");
  23. else
  24. builder.appendff("<code class=\"language-{}\">", escape_html_entities(m_language));
  25. if (m_language == "js")
  26. builder.append(JS::MarkupGenerator::html_from_source(m_code));
  27. else
  28. builder.append(escape_html_entities(m_code));
  29. builder.append("</code>");
  30. if (m_style.length() >= 2)
  31. builder.append("</strong>");
  32. else if (m_style.length() >= 2)
  33. builder.append("</em>");
  34. builder.append("</pre>\n");
  35. return builder.build();
  36. }
  37. String CodeBlock::render_for_terminal(size_t) const
  38. {
  39. StringBuilder builder;
  40. for (auto line : m_code.split('\n')) {
  41. builder.append(" ");
  42. builder.append(line);
  43. builder.append("\n");
  44. }
  45. return builder.build();
  46. }
  47. RecursionDecision CodeBlock::walk(Visitor& visitor) const
  48. {
  49. RecursionDecision rd = visitor.visit(*this);
  50. if (rd != RecursionDecision::Recurse)
  51. return rd;
  52. rd = visitor.visit(m_code);
  53. if (rd != RecursionDecision::Recurse)
  54. return rd;
  55. // Don't recurse on m_language and m_style.
  56. // Normalize return value.
  57. return RecursionDecision::Continue;
  58. }
  59. static Regex<ECMA262> open_fence_re("^ {0,3}(([\\`\\~])\\2{2,})\\s*([\\*_]*)\\s*([^\\*_\\s]*).*$");
  60. static Regex<ECMA262> close_fence_re("^ {0,3}(([\\`\\~])\\2{2,})\\s*$");
  61. static Optional<int> line_block_prefix(StringView const& line)
  62. {
  63. int characters = 0;
  64. int indents = 0;
  65. for (char ch : line) {
  66. if (indents == 4)
  67. break;
  68. if (ch == ' ') {
  69. ++characters;
  70. ++indents;
  71. } else if (ch == '\t') {
  72. ++characters;
  73. indents = 4;
  74. } else {
  75. break;
  76. }
  77. }
  78. if (indents == 4)
  79. return characters;
  80. return {};
  81. }
  82. OwnPtr<CodeBlock> CodeBlock::parse(LineIterator& lines)
  83. {
  84. if (lines.is_end())
  85. return {};
  86. StringView line = *lines;
  87. if (open_fence_re.match(line).success)
  88. return parse_backticks(lines);
  89. if (line_block_prefix(line).has_value())
  90. return parse_indent(lines);
  91. return {};
  92. }
  93. OwnPtr<CodeBlock> CodeBlock::parse_backticks(LineIterator& lines)
  94. {
  95. StringView line = *lines;
  96. // Our Markdown extension: we allow
  97. // specifying a style and a language
  98. // for a code block, like so:
  99. //
  100. // ```**sh**
  101. // $ echo hello friends!
  102. // ````
  103. //
  104. // The code block will be made bold,
  105. // and if possible syntax-highlighted
  106. // as appropriate for a shell script.
  107. auto matches = open_fence_re.match(line).capture_group_matches[0];
  108. auto fence = matches[0].view.string_view();
  109. auto style = matches[2].view.string_view();
  110. auto language = matches[3].view.string_view();
  111. ++lines;
  112. StringBuilder builder;
  113. while (true) {
  114. if (lines.is_end())
  115. break;
  116. line = *lines;
  117. ++lines;
  118. auto close_match = close_fence_re.match(line);
  119. if (close_match.success) {
  120. auto close_fence = close_match.capture_group_matches[0][0].view.string_view();
  121. if (close_fence[0] == fence[0] && close_fence.length() >= fence.length())
  122. break;
  123. }
  124. builder.append(line);
  125. builder.append('\n');
  126. }
  127. return make<CodeBlock>(language, style, builder.build());
  128. }
  129. OwnPtr<CodeBlock> CodeBlock::parse_indent(LineIterator& lines)
  130. {
  131. StringBuilder builder;
  132. while (true) {
  133. if (lines.is_end())
  134. break;
  135. StringView line = *lines;
  136. auto prefix_length = line_block_prefix(line);
  137. if (!prefix_length.has_value())
  138. break;
  139. line = line.substring_view(prefix_length.value());
  140. ++lines;
  141. builder.append(line);
  142. builder.append('\n');
  143. }
  144. return make<CodeBlock>("", "", builder.build());
  145. }
  146. }