SourceHighlighter.cpp 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /*
  2. * Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/StringBuilder.h>
  7. #include <LibURL/URL.h>
  8. #include <LibWeb/HTML/Parser/HTMLTokenizer.h>
  9. #include <LibWebView/SourceHighlighter.h>
  10. namespace WebView {
  11. static String generate_style()
  12. {
  13. StringBuilder builder;
  14. builder.append(HTML_HIGHLIGHTER_STYLE);
  15. builder.append(R"~~~(
  16. .html {
  17. counter-reset: line;
  18. }
  19. .line {
  20. counter-increment: line;
  21. white-space: nowrap;
  22. }
  23. .line::before {
  24. content: counter(line) " ";
  25. display: inline-block;
  26. width: 2.5em;
  27. padding-right: 0.5em;
  28. text-align: right;
  29. }
  30. @media (prefers-color-scheme: dark) {
  31. .line::before {
  32. color: darkgrey;
  33. }
  34. }
  35. @media (prefers-color-scheme: light) {
  36. .line::before {
  37. color: dimgray;
  38. }
  39. }
  40. )~~~"sv);
  41. return MUST(builder.to_string());
  42. }
  43. String highlight_source(URL::URL const& url, StringView source)
  44. {
  45. Web::HTML::HTMLTokenizer tokenizer { source, "utf-8"sv };
  46. StringBuilder builder;
  47. builder.append(R"~~~(
  48. <!DOCTYPE html>
  49. <html>
  50. <head>
  51. <meta name="color-scheme" content="dark light">)~~~"sv);
  52. builder.appendff("<title>View Source - {}</title>", url);
  53. builder.appendff("<style type=\"text/css\">{}</style>", generate_style());
  54. builder.append(R"~~~(
  55. </head>
  56. <body>
  57. <pre class="html">
  58. <span class="line">)~~~"sv);
  59. size_t previous_position = 0;
  60. auto append_source = [&](auto end_position, Optional<StringView> const& class_name = {}) {
  61. if (end_position <= previous_position)
  62. return;
  63. auto segment = source.substring_view(previous_position, end_position - previous_position);
  64. auto append_class_start = [&]() {
  65. if (class_name.has_value())
  66. builder.appendff("<span class=\"{}\">"sv, *class_name);
  67. };
  68. auto append_class_end = [&]() {
  69. if (class_name.has_value())
  70. builder.append("</span>"sv);
  71. };
  72. append_class_start();
  73. for (auto code_point : Utf8View { segment }) {
  74. if (code_point == '&') {
  75. builder.append("&amp;"sv);
  76. } else if (code_point == 0xA0) {
  77. builder.append("&nbsp;"sv);
  78. } else if (code_point == '<') {
  79. builder.append("&lt;"sv);
  80. } else if (code_point == '>') {
  81. builder.append("&gt;"sv);
  82. } else if (code_point == '\n') {
  83. append_class_end();
  84. builder.append("</span>\n<span class=\"line\">"sv);
  85. append_class_start();
  86. } else {
  87. builder.append_code_point(code_point);
  88. }
  89. }
  90. append_class_end();
  91. previous_position = end_position;
  92. };
  93. for (auto token = tokenizer.next_token(); token.has_value(); token = tokenizer.next_token()) {
  94. if (token->is_comment()) {
  95. append_source(token->start_position().byte_offset);
  96. append_source(token->end_position().byte_offset, "comment"sv);
  97. } else if (token->is_start_tag() || token->is_end_tag()) {
  98. auto tag_name_start = token->start_position().byte_offset;
  99. append_source(tag_name_start);
  100. append_source(tag_name_start + token->tag_name().bytes().size(), "tag"sv);
  101. token->for_each_attribute([&](auto const& attribute) {
  102. append_source(attribute.name_start_position.byte_offset);
  103. append_source(attribute.name_end_position.byte_offset, "attribute-name"sv);
  104. append_source(attribute.value_start_position.byte_offset);
  105. append_source(attribute.value_end_position.byte_offset, "attribute-value"sv);
  106. return IterationDecision::Continue;
  107. });
  108. append_source(token->end_position().byte_offset);
  109. } else {
  110. append_source(token->end_position().byte_offset);
  111. if (token->is_end_of_file())
  112. break;
  113. }
  114. }
  115. builder.append(R"~~~(
  116. </span>
  117. </pre>
  118. </body>
  119. </html>
  120. )~~~"sv);
  121. return MUST(builder.to_string());
  122. }
  123. }