CodeBlock.cpp 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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. for (auto line : m_code.split('\n')) {
  40. builder.append(" ");
  41. builder.append(line);
  42. builder.append("\n");
  43. }
  44. return builder.build();
  45. }
  46. RecursionDecision CodeBlock::walk(Visitor& visitor) const
  47. {
  48. RecursionDecision rd = visitor.visit(*this);
  49. if (rd != RecursionDecision::Recurse)
  50. return rd;
  51. rd = visitor.visit(m_code);
  52. if (rd != RecursionDecision::Recurse)
  53. return rd;
  54. // Don't recurse on m_language and m_style.
  55. // Normalize return value.
  56. return RecursionDecision::Continue;
  57. }
  58. static Regex<ECMA262> style_spec_re("\\s*([\\*_]*)\\s*([^\\*_\\s]*).*");
  59. OwnPtr<CodeBlock> CodeBlock::parse(LineIterator& lines)
  60. {
  61. if (lines.is_end())
  62. return {};
  63. constexpr auto tick_tick_tick = "```";
  64. StringView line = *lines;
  65. if (!line.starts_with(tick_tick_tick))
  66. return {};
  67. // Our Markdown extension: we allow
  68. // specifying a style and a language
  69. // for a code block, like so:
  70. //
  71. // ```**sh**
  72. // $ echo hello friends!
  73. // ````
  74. //
  75. // The code block will be made bold,
  76. // and if possible syntax-highlighted
  77. // as appropriate for a shell script.
  78. StringView style_spec = line.substring_view(3, line.length() - 3);
  79. auto matches = style_spec_re.match(style_spec);
  80. auto style = matches.capture_group_matches[0][0].view.string_view();
  81. auto language = matches.capture_group_matches[0][1].view.string_view();
  82. ++lines;
  83. bool first = true;
  84. StringBuilder builder;
  85. while (true) {
  86. if (lines.is_end())
  87. break;
  88. line = *lines;
  89. ++lines;
  90. if (line == tick_tick_tick)
  91. break;
  92. if (!first)
  93. builder.append('\n');
  94. builder.append(line);
  95. first = false;
  96. }
  97. return make<CodeBlock>(language, style, builder.build());
  98. }
  99. }