SyntaxHighlighter.cpp 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. * Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/Debug.h>
  8. #include <LibGfx/Palette.h>
  9. #include <LibJS/Lexer.h>
  10. #include <LibJS/SyntaxHighlighter.h>
  11. #include <LibJS/Token.h>
  12. namespace JS {
  13. static Gfx::TextAttributes style_for_token_type(Gfx::Palette const& palette, TokenType type)
  14. {
  15. switch (Token::category(type)) {
  16. case TokenCategory::Invalid:
  17. return { palette.syntax_comment() };
  18. case TokenCategory::Number:
  19. return { palette.syntax_number() };
  20. case TokenCategory::String:
  21. return { palette.syntax_string() };
  22. case TokenCategory::Punctuation:
  23. return { palette.syntax_punctuation() };
  24. case TokenCategory::Operator:
  25. return { palette.syntax_operator() };
  26. case TokenCategory::Keyword:
  27. return { palette.syntax_keyword(), {}, true };
  28. case TokenCategory::ControlKeyword:
  29. return { palette.syntax_control_keyword(), {}, true };
  30. case TokenCategory::Identifier:
  31. return { palette.syntax_identifier() };
  32. default:
  33. return { palette.base_text() };
  34. }
  35. }
  36. bool SyntaxHighlighter::is_identifier(u64 token) const
  37. {
  38. auto js_token = static_cast<TokenType>(static_cast<size_t>(token));
  39. return js_token == TokenType::Identifier;
  40. }
  41. bool SyntaxHighlighter::is_navigatable([[maybe_unused]] u64 token) const
  42. {
  43. return false;
  44. }
  45. void SyntaxHighlighter::rehighlight(Palette const& palette)
  46. {
  47. auto text = m_client->get_text();
  48. Lexer lexer(text);
  49. Vector<Syntax::TextDocumentSpan> spans;
  50. Vector<Syntax::TextDocumentFoldingRegion> folding_regions;
  51. Syntax::TextPosition position { 0, 0 };
  52. Syntax::TextPosition start { 0, 0 };
  53. auto advance_position = [&position](u32 code_point) {
  54. if (code_point == '\n') {
  55. position.set_line(position.line() + 1);
  56. position.set_column(0);
  57. } else
  58. position.set_column(position.column() + 1);
  59. };
  60. auto append_token = [&](Utf8View str, Token const& token, bool is_trivia) {
  61. if (str.is_empty())
  62. return;
  63. start = position;
  64. for (auto code_point : str)
  65. advance_position(code_point);
  66. Syntax::TextDocumentSpan span;
  67. span.range.set_start(start);
  68. span.range.set_end({ position.line(), position.column() });
  69. auto type = is_trivia ? TokenType::Invalid : token.type();
  70. span.attributes = style_for_token_type(palette, type);
  71. span.is_skippable = is_trivia;
  72. span.data = static_cast<u64>(type);
  73. spans.append(span);
  74. dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "{}{} @ '{}' {}:{} - {}:{}",
  75. token.name(),
  76. is_trivia ? " (trivia)" : "",
  77. token.value(),
  78. span.range.start().line(), span.range.start().column(),
  79. span.range.end().line(), span.range.end().column());
  80. };
  81. struct TokenData {
  82. Token token;
  83. Syntax::TextRange range;
  84. };
  85. Vector<TokenData> folding_region_start_tokens;
  86. bool was_eof = false;
  87. for (auto token = lexer.next(); !was_eof; token = lexer.next()) {
  88. append_token(Utf8View(token.trivia()), token, true);
  89. auto token_start_position = position;
  90. append_token(Utf8View(token.value()), token, false);
  91. if (token.type() == TokenType::Eof)
  92. was_eof = true;
  93. // Create folding regions for {} blocks
  94. if (token.type() == TokenType::CurlyOpen) {
  95. folding_region_start_tokens.append({ .token = token,
  96. .range = { token_start_position, position } });
  97. } else if (token.type() == TokenType::CurlyClose) {
  98. if (!folding_region_start_tokens.is_empty()) {
  99. auto curly_open = folding_region_start_tokens.take_last();
  100. Syntax::TextDocumentFoldingRegion region;
  101. region.range.set_start(curly_open.range.end());
  102. region.range.set_end(token_start_position);
  103. folding_regions.append(region);
  104. }
  105. }
  106. }
  107. m_client->do_set_spans(move(spans));
  108. m_client->do_set_folding_regions(move(folding_regions));
  109. m_has_brace_buddies = false;
  110. highlight_matching_token_pair();
  111. m_client->do_update();
  112. }
  113. Vector<Syntax::Highlighter::MatchingTokenPair> SyntaxHighlighter::matching_token_pairs_impl() const
  114. {
  115. static Vector<Syntax::Highlighter::MatchingTokenPair> pairs;
  116. if (pairs.is_empty()) {
  117. pairs.append({ static_cast<u64>(TokenType::CurlyOpen), static_cast<u64>(TokenType::CurlyClose) });
  118. pairs.append({ static_cast<u64>(TokenType::ParenOpen), static_cast<u64>(TokenType::ParenClose) });
  119. pairs.append({ static_cast<u64>(TokenType::BracketOpen), static_cast<u64>(TokenType::BracketClose) });
  120. }
  121. return pairs;
  122. }
  123. bool SyntaxHighlighter::token_types_equal(u64 token1, u64 token2) const
  124. {
  125. return static_cast<TokenType>(token1) == static_cast<TokenType>(token2);
  126. }
  127. }