CodeBlock.cpp 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. /*
  2. * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/StringBuilder.h>
  7. #include <LibJS/MarkupGenerator.h>
  8. #include <LibMarkdown/CodeBlock.h>
  9. #include <LibMarkdown/Visitor.h>
  10. #include <LibRegex/Regex.h>
  11. namespace Markdown {
  12. String CodeBlock::render_to_html(bool) const
  13. {
  14. StringBuilder builder;
  15. builder.append("<pre>");
  16. if (m_style.length() >= 2)
  17. builder.append("<strong>");
  18. else if (m_style.length() >= 2)
  19. builder.append("<em>");
  20. if (m_language.is_empty())
  21. builder.append("<code>");
  22. else
  23. builder.appendff("<code class=\"language-{}\">", escape_html_entities(m_language));
  24. if (m_language == "js")
  25. builder.append(JS::MarkupGenerator::html_from_source(m_code));
  26. else
  27. builder.append(escape_html_entities(m_code));
  28. builder.append("\n</code>");
  29. if (m_style.length() >= 2)
  30. builder.append("</strong>");
  31. else if (m_style.length() >= 2)
  32. builder.append("</em>");
  33. builder.append("</pre>\n");
  34. return builder.build();
  35. }
  36. String CodeBlock::render_for_terminal(size_t) const
  37. {
  38. StringBuilder builder;
  39. builder.append(m_code);
  40. builder.append("\n\n");
  41. return builder.build();
  42. }
  43. RecursionDecision CodeBlock::walk(Visitor& visitor) const
  44. {
  45. RecursionDecision rd = visitor.visit(*this);
  46. if (rd != RecursionDecision::Recurse)
  47. return rd;
  48. rd = visitor.visit(m_code);
  49. if (rd != RecursionDecision::Recurse)
  50. return rd;
  51. // Don't recurse on m_language and m_style.
  52. // Normalize return value.
  53. return RecursionDecision::Continue;
  54. }
  55. static Regex<ECMA262> style_spec_re("\\s*([\\*_]*)\\s*([^\\*_\\s]*).*");
  56. OwnPtr<CodeBlock> CodeBlock::parse(LineIterator& lines)
  57. {
  58. if (lines.is_end())
  59. return {};
  60. constexpr auto tick_tick_tick = "```";
  61. StringView line = *lines;
  62. if (!line.starts_with(tick_tick_tick))
  63. return {};
  64. // Our Markdown extension: we allow
  65. // specifying a style and a language
  66. // for a code block, like so:
  67. //
  68. // ```**sh**
  69. // $ echo hello friends!
  70. // ````
  71. //
  72. // The code block will be made bold,
  73. // and if possible syntax-highlighted
  74. // as appropriate for a shell script.
  75. StringView style_spec = line.substring_view(3, line.length() - 3);
  76. auto matches = style_spec_re.match(style_spec);
  77. auto style = matches.capture_group_matches[0][0].view.string_view();
  78. auto language = matches.capture_group_matches[0][1].view.string_view();
  79. ++lines;
  80. bool first = true;
  81. StringBuilder builder;
  82. while (true) {
  83. if (lines.is_end())
  84. break;
  85. line = *lines;
  86. ++lines;
  87. if (line == tick_tick_tick)
  88. break;
  89. if (!first)
  90. builder.append('\n');
  91. builder.append(line);
  92. first = false;
  93. }
  94. return make<CodeBlock>(language, style, builder.build());
  95. }
  96. }