Serenity Operating System
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}