Serenity Operating System
at master 153 lines 5.1 kB view raw
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 8#include <AK/Debug.h> 9#include <LibGfx/Palette.h> 10#include <LibJS/Lexer.h> 11#include <LibJS/SyntaxHighlighter.h> 12#include <LibJS/Token.h> 13 14namespace JS { 15 16static Syntax::TextStyle style_for_token_type(Gfx::Palette const& palette, TokenType type) 17{ 18 switch (Token::category(type)) { 19 case TokenCategory::Invalid: 20 return { palette.syntax_comment() }; 21 case TokenCategory::Number: 22 return { palette.syntax_number() }; 23 case TokenCategory::String: 24 return { palette.syntax_string() }; 25 case TokenCategory::Punctuation: 26 return { palette.syntax_punctuation() }; 27 case TokenCategory::Operator: 28 return { palette.syntax_operator() }; 29 case TokenCategory::Keyword: 30 return { palette.syntax_keyword(), true }; 31 case TokenCategory::ControlKeyword: 32 return { palette.syntax_control_keyword(), true }; 33 case TokenCategory::Identifier: 34 return { palette.syntax_identifier() }; 35 default: 36 return { palette.base_text() }; 37 } 38} 39 40bool SyntaxHighlighter::is_identifier(u64 token) const 41{ 42 auto js_token = static_cast<TokenType>(static_cast<size_t>(token)); 43 return js_token == TokenType::Identifier; 44} 45 46bool SyntaxHighlighter::is_navigatable([[maybe_unused]] u64 token) const 47{ 48 return false; 49} 50 51void SyntaxHighlighter::rehighlight(Palette const& palette) 52{ 53 auto text = m_client->get_text(); 54 55 Lexer lexer(text); 56 57 Vector<GUI::TextDocumentSpan> spans; 58 Vector<GUI::TextDocumentFoldingRegion> folding_regions; 59 GUI::TextPosition position { 0, 0 }; 60 GUI::TextPosition start { 0, 0 }; 61 62 auto advance_position = [&position](char ch) { 63 if (ch == '\n') { 64 position.set_line(position.line() + 1); 65 position.set_column(0); 66 } else 67 position.set_column(position.column() + 1); 68 }; 69 70 auto append_token = [&](StringView str, Token const& token, bool is_trivia) { 71 if (str.is_empty()) 72 return; 73 74 start = position; 75 for (size_t i = 0; i < str.length(); ++i) 76 advance_position(str[i]); 77 78 GUI::TextDocumentSpan span; 79 span.range.set_start(start); 80 span.range.set_end({ position.line(), position.column() }); 81 auto type = is_trivia ? TokenType::Invalid : token.type(); 82 auto style = style_for_token_type(palette, type); 83 span.attributes.color = style.color; 84 span.attributes.bold = style.bold; 85 span.is_skippable = is_trivia; 86 span.data = static_cast<u64>(type); 87 spans.append(span); 88 89 dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "{}{} @ '{}' {}:{} - {}:{}", 90 token.name(), 91 is_trivia ? " (trivia)" : "", 92 token.value(), 93 span.range.start().line(), span.range.start().column(), 94 span.range.end().line(), span.range.end().column()); 95 }; 96 97 struct TokenData { 98 Token token; 99 GUI::TextRange range; 100 }; 101 Vector<TokenData> folding_region_start_tokens; 102 103 bool was_eof = false; 104 for (auto token = lexer.next(); !was_eof; token = lexer.next()) { 105 append_token(token.trivia(), token, true); 106 107 auto token_start_position = position; 108 append_token(token.value(), token, false); 109 110 if (token.type() == TokenType::Eof) 111 was_eof = true; 112 113 // Create folding regions for {} blocks 114 if (token.type() == TokenType::CurlyOpen) { 115 folding_region_start_tokens.append({ .token = token, 116 .range = { token_start_position, position } }); 117 } else if (token.type() == TokenType::CurlyClose) { 118 if (!folding_region_start_tokens.is_empty()) { 119 auto curly_open = folding_region_start_tokens.take_last(); 120 GUI::TextDocumentFoldingRegion region; 121 region.range.set_start(curly_open.range.end()); 122 region.range.set_end(token_start_position); 123 folding_regions.append(region); 124 } 125 } 126 } 127 128 m_client->do_set_spans(move(spans)); 129 m_client->do_set_folding_regions(move(folding_regions)); 130 131 m_has_brace_buddies = false; 132 highlight_matching_token_pair(); 133 134 m_client->do_update(); 135} 136 137Vector<Syntax::Highlighter::MatchingTokenPair> SyntaxHighlighter::matching_token_pairs_impl() const 138{ 139 static Vector<Syntax::Highlighter::MatchingTokenPair> pairs; 140 if (pairs.is_empty()) { 141 pairs.append({ static_cast<u64>(TokenType::CurlyOpen), static_cast<u64>(TokenType::CurlyClose) }); 142 pairs.append({ static_cast<u64>(TokenType::ParenOpen), static_cast<u64>(TokenType::ParenClose) }); 143 pairs.append({ static_cast<u64>(TokenType::BracketOpen), static_cast<u64>(TokenType::BracketClose) }); 144 } 145 return pairs; 146} 147 148bool SyntaxHighlighter::token_types_equal(u64 token1, u64 token2) const 149{ 150 return static_cast<TokenType>(token1) == static_cast<TokenType>(token2); 151} 152 153}