SourceHighlighter.cpp 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  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 <AK/URL.h>
  8. #include <LibWeb/HTML/Parser/HTMLTokenizer.h>
  9. #include <LibWebView/SourceHighlighter.h>
  10. namespace WebView {
  11. String highlight_source(URL const& url, StringView source)
  12. {
  13. Web::HTML::HTMLTokenizer tokenizer { source, "utf-8"sv };
  14. StringBuilder builder;
  15. builder.append(R"~~~(
  16. <!DOCTYPE html>
  17. <html>
  18. <head>
  19. <meta name="color-scheme" content="dark light">)~~~"sv);
  20. builder.appendff("<title>View Source - {}</title>", url);
  21. builder.append(R"~~~(
  22. <style type="text/css">
  23. html {
  24. font-size: 10pt;
  25. }
  26. @media (prefers-color-scheme: dark) {
  27. /* FIXME: We should be able to remove the HTML style when "color-scheme" is supported */
  28. html {
  29. background-color: rgb(30, 30, 30);
  30. color: white;
  31. }
  32. .comment {
  33. color: lightgreen;
  34. }
  35. .tag {
  36. color: orangered;
  37. }
  38. .attribute-name {
  39. color: orange;
  40. }
  41. .attribute-value {
  42. color: deepskyblue;
  43. }
  44. }
  45. @media (prefers-color-scheme: light) {
  46. .comment {
  47. color: green;
  48. }
  49. .tag {
  50. color: red;
  51. }
  52. .attribute-name {
  53. color: darkorange;
  54. }
  55. .attribute-value {
  56. color: blue;
  57. }
  58. }
  59. </style>
  60. </head>
  61. <body>
  62. <pre>
  63. )~~~"sv);
  64. size_t previous_position = 0;
  65. auto append_source = [&](auto end_position, Optional<StringView> const& class_name = {}) {
  66. if (end_position <= previous_position)
  67. return;
  68. auto segment = source.substring_view(previous_position, end_position - previous_position);
  69. if (class_name.has_value())
  70. builder.appendff("<span class=\"{}\">"sv, *class_name);
  71. for (auto code_point : Utf8View { segment }) {
  72. if (code_point == '&')
  73. builder.append("&amp;"sv);
  74. else if (code_point == 0xA0)
  75. builder.append("&nbsp;"sv);
  76. else if (code_point == '<')
  77. builder.append("&lt;"sv);
  78. else if (code_point == '>')
  79. builder.append("&gt;"sv);
  80. else
  81. builder.append_code_point(code_point);
  82. }
  83. if (class_name.has_value())
  84. builder.append("</span>"sv);
  85. previous_position = end_position;
  86. };
  87. for (auto token = tokenizer.next_token(); token.has_value(); token = tokenizer.next_token()) {
  88. if (token->is_comment()) {
  89. append_source(token->start_position().byte_offset);
  90. append_source(token->end_position().byte_offset, "comment"sv);
  91. } else if (token->is_start_tag() || token->is_end_tag()) {
  92. auto tag_name_start = token->start_position().byte_offset;
  93. append_source(tag_name_start);
  94. append_source(tag_name_start + token->tag_name().bytes().size(), "tag"sv);
  95. token->for_each_attribute([&](auto const& attribute) {
  96. append_source(attribute.name_start_position.byte_offset);
  97. append_source(attribute.name_end_position.byte_offset, "attribute-name"sv);
  98. append_source(attribute.value_start_position.byte_offset);
  99. append_source(attribute.value_end_position.byte_offset, "attribute-value"sv);
  100. return IterationDecision::Continue;
  101. });
  102. append_source(token->end_position().byte_offset);
  103. } else {
  104. append_source(token->end_position().byte_offset);
  105. }
  106. }
  107. builder.append(R"~~~(
  108. </pre>
  109. </body>
  110. </html>
  111. )~~~"sv);
  112. return MUST(builder.to_string());
  113. }
  114. }