SyntaxHighlighter.cpp 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. /*
  2. * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
  3. * Copyright (c) 2021, Max Wipfli <mail@maxwipfli.ch>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/Debug.h>
  8. #include <LibWeb/HTML/Parser/HTMLTokenizer.h>
  9. #include <LibWeb/HTML/SyntaxHighlighter/SyntaxHighlighter.h>
  10. namespace Web::HTML {
  11. enum class AugmentedTokenKind : u32 {
  12. AttributeName,
  13. AttributeValue,
  14. OpenTag,
  15. CloseTag,
  16. Comment,
  17. Doctype,
  18. };
  19. bool SyntaxHighlighter::is_identifier(void* token) const
  20. {
  21. if (!token)
  22. return false;
  23. return false;
  24. }
  25. bool SyntaxHighlighter::is_navigatable(void*) const
  26. {
  27. return false;
  28. }
  29. void SyntaxHighlighter::rehighlight(Palette const& palette)
  30. {
  31. dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(HTML::SyntaxHighlighter) starting rehighlight");
  32. (void)palette;
  33. auto text = m_client->get_text();
  34. Vector<GUI::TextDocumentSpan> spans;
  35. auto highlight = [&](auto start_line, auto start_column, auto end_line, auto end_column, Gfx::TextAttributes attributes, AugmentedTokenKind kind) {
  36. if (start_line > end_line || (start_line == end_line && start_column >= end_column)) {
  37. dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(HTML::SyntaxHighlighter) discarding ({}-{}) to ({}-{}) because it has zero or negative length", start_line, start_column, end_line, end_column);
  38. return;
  39. }
  40. dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(HTML::SyntaxHighlighter) highlighting ({}-{}) to ({}-{}) with color {}", start_line, start_column, end_line, end_column, attributes.color);
  41. spans.empend(
  42. GUI::TextRange {
  43. { start_line, start_column },
  44. { end_line, end_column },
  45. },
  46. move(attributes),
  47. (void*)kind,
  48. false);
  49. };
  50. HTMLTokenizer tokenizer { text, "utf-8" };
  51. [[maybe_unused]] enum class State {
  52. HTML,
  53. Javascript,
  54. CSS,
  55. } state { State::HTML };
  56. for (;;) {
  57. auto token = tokenizer.next_token();
  58. if (!token.has_value() || token.value().is_end_of_file())
  59. break;
  60. dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(HTML::SyntaxHighlighter) got token of type {}", token->to_string());
  61. if (token->is_start_tag()) {
  62. if (token->tag_name() == "script"sv) {
  63. tokenizer.switch_to(HTMLTokenizer::State::ScriptData);
  64. state = State::Javascript;
  65. } else if (token->tag_name() == "style"sv) {
  66. tokenizer.switch_to(HTMLTokenizer::State::RAWTEXT);
  67. state = State::CSS;
  68. }
  69. } else if (token->is_end_tag()) {
  70. if (token->tag_name().is_one_of("script"sv, "style"sv)) {
  71. if (state == State::Javascript) {
  72. // FIXME: Highlight javascript code here instead.
  73. } else if (state == State::CSS) {
  74. // FIXME: Highlight CSS code here instead.
  75. }
  76. state = State::HTML;
  77. }
  78. }
  79. size_t token_start_offset = token->is_end_tag() ? 1 : 0;
  80. if (token->is_comment()) {
  81. highlight(
  82. token->start_position().line,
  83. token->start_position().column,
  84. token->end_position().line,
  85. token->end_position().column,
  86. { palette.syntax_comment(), {} },
  87. AugmentedTokenKind::Comment);
  88. } else if (token->is_start_tag() || token->is_end_tag()) {
  89. highlight(
  90. token->start_position().line,
  91. token->start_position().column + token_start_offset,
  92. token->start_position().line,
  93. token->start_position().column + token_start_offset + token->tag_name().length(),
  94. { palette.syntax_keyword(), {}, false, true },
  95. token->is_start_tag() ? AugmentedTokenKind::OpenTag : AugmentedTokenKind::CloseTag);
  96. for (auto& attribute : token->attributes()) {
  97. highlight(
  98. attribute.name_start_position.line,
  99. attribute.name_start_position.column + token_start_offset,
  100. attribute.name_end_position.line,
  101. attribute.name_end_position.column + token_start_offset,
  102. { palette.syntax_identifier(), {} },
  103. AugmentedTokenKind::AttributeName);
  104. highlight(
  105. attribute.value_start_position.line,
  106. attribute.value_start_position.column + token_start_offset,
  107. attribute.value_end_position.line,
  108. attribute.value_end_position.column + token_start_offset,
  109. { palette.syntax_string(), {} },
  110. AugmentedTokenKind::AttributeValue);
  111. }
  112. } else if (token->is_doctype()) {
  113. highlight(
  114. token->start_position().line,
  115. token->start_position().column,
  116. token->start_position().line,
  117. token->start_position().column,
  118. { palette.syntax_preprocessor_statement(), {} },
  119. AugmentedTokenKind::Doctype);
  120. }
  121. }
  122. if constexpr (SYNTAX_HIGHLIGHTING_DEBUG) {
  123. dbgln("(HTML::SyntaxHighlighter) list of all spans:");
  124. for (auto& span : spans)
  125. dbgln("{}, {}", span.range, span.attributes.color);
  126. dbgln("(HTML::SyntaxHighlighter) end of list");
  127. }
  128. m_client->do_set_spans(move(spans));
  129. m_has_brace_buddies = false;
  130. highlight_matching_token_pair();
  131. m_client->do_update();
  132. }
  133. Vector<Syntax::Highlighter::MatchingTokenPair> SyntaxHighlighter::matching_token_pairs() const
  134. {
  135. static Vector<MatchingTokenPair> pairs;
  136. if (pairs.is_empty()) {
  137. pairs.append({ (void*)AugmentedTokenKind::OpenTag, (void*)AugmentedTokenKind::CloseTag });
  138. }
  139. return pairs;
  140. }
  141. bool SyntaxHighlighter::token_types_equal(void* token0, void* token1) const
  142. {
  143. return token0 == token1;
  144. }
  145. }