Serenity Operating System
at hosted 230 lines 8.3 kB view raw
1/* 2 * Copyright (c) 2020, the SerenityOS developers. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <LibGUI/JSSyntaxHighlighter.h> 28#include <LibGUI/TextEditor.h> 29#include <LibGfx/Font.h> 30#include <LibGfx/Palette.h> 31#include <LibJS/Lexer.h> 32#include <LibJS/Token.h> 33 34namespace GUI { 35 36static TextStyle style_for_token_type(Gfx::Palette palette, JS::TokenType type) 37{ 38 switch (type) { 39 case JS::TokenType::Invalid: 40 case JS::TokenType::Eof: 41 return { palette.syntax_comment() }; 42 case JS::TokenType::NumericLiteral: 43 return { palette.syntax_number() }; 44 case JS::TokenType::StringLiteral: 45 case JS::TokenType::RegexLiteral: 46 case JS::TokenType::UnterminatedStringLiteral: 47 return { palette.syntax_string() }; 48 case JS::TokenType::BracketClose: 49 case JS::TokenType::BracketOpen: 50 case JS::TokenType::Caret: 51 case JS::TokenType::Comma: 52 case JS::TokenType::CurlyClose: 53 case JS::TokenType::CurlyOpen: 54 case JS::TokenType::ParenClose: 55 case JS::TokenType::ParenOpen: 56 case JS::TokenType::Semicolon: 57 return { palette.syntax_punctuation() }; 58 case JS::TokenType::Ampersand: 59 case JS::TokenType::AmpersandEquals: 60 case JS::TokenType::Asterisk: 61 case JS::TokenType::AsteriskAsteriskEquals: 62 case JS::TokenType::AsteriskEquals: 63 case JS::TokenType::DoubleAmpersand: 64 case JS::TokenType::DoubleAsterisk: 65 case JS::TokenType::DoublePipe: 66 case JS::TokenType::DoubleQuestionMark: 67 case JS::TokenType::Equals: 68 case JS::TokenType::EqualsEquals: 69 case JS::TokenType::EqualsEqualsEquals: 70 case JS::TokenType::ExclamationMark: 71 case JS::TokenType::ExclamationMarkEquals: 72 case JS::TokenType::ExclamationMarkEqualsEquals: 73 case JS::TokenType::GreaterThan: 74 case JS::TokenType::GreaterThanEquals: 75 case JS::TokenType::LessThan: 76 case JS::TokenType::LessThanEquals: 77 case JS::TokenType::Minus: 78 case JS::TokenType::MinusEquals: 79 case JS::TokenType::MinusMinus: 80 case JS::TokenType::Percent: 81 case JS::TokenType::PercentEquals: 82 case JS::TokenType::Period: 83 case JS::TokenType::Pipe: 84 case JS::TokenType::PipeEquals: 85 case JS::TokenType::Plus: 86 case JS::TokenType::PlusEquals: 87 case JS::TokenType::PlusPlus: 88 case JS::TokenType::QuestionMark: 89 case JS::TokenType::QuestionMarkPeriod: 90 case JS::TokenType::ShiftLeft: 91 case JS::TokenType::ShiftLeftEquals: 92 case JS::TokenType::ShiftRight: 93 case JS::TokenType::ShiftRightEquals: 94 case JS::TokenType::Slash: 95 case JS::TokenType::SlashEquals: 96 case JS::TokenType::Tilde: 97 case JS::TokenType::UnsignedShiftRight: 98 case JS::TokenType::UnsignedShiftRightEquals: 99 return { palette.syntax_operator() }; 100 case JS::TokenType::BoolLiteral: 101 case JS::TokenType::Class: 102 case JS::TokenType::Const: 103 case JS::TokenType::Delete: 104 case JS::TokenType::Function: 105 case JS::TokenType::In: 106 case JS::TokenType::Instanceof: 107 case JS::TokenType::Interface: 108 case JS::TokenType::Let: 109 case JS::TokenType::New: 110 case JS::TokenType::NullLiteral: 111 case JS::TokenType::Typeof: 112 case JS::TokenType::Var: 113 case JS::TokenType::Void: 114 return { palette.syntax_keyword(), &Gfx::Font::default_bold_fixed_width_font() }; 115 case JS::TokenType::Await: 116 case JS::TokenType::Catch: 117 case JS::TokenType::Do: 118 case JS::TokenType::Else: 119 case JS::TokenType::Finally: 120 case JS::TokenType::For: 121 case JS::TokenType::If: 122 case JS::TokenType::Return: 123 case JS::TokenType::Try: 124 case JS::TokenType::While: 125 case JS::TokenType::Yield: 126 return { palette.syntax_control_keyword(), &Gfx::Font::default_bold_fixed_width_font() }; 127 case JS::TokenType::Identifier: 128 return { palette.syntax_identifier() }; 129 130 default: 131 return { palette.base_text() }; 132 } 133} 134 135bool JSSyntaxHighlighter::is_identifier(void* token) const 136{ 137 auto js_token = static_cast<JS::TokenType>(reinterpret_cast<size_t>(token)); 138 return js_token == JS::TokenType::Identifier; 139} 140 141bool JSSyntaxHighlighter::is_navigatable(void* token) const 142{ 143 (void)token; 144 return false; 145} 146 147void JSSyntaxHighlighter::rehighlight(Gfx::Palette palette) 148{ 149 ASSERT(m_editor); 150 auto text = m_editor->text(); 151 152 JS::Lexer lexer(text); 153 154 Vector<GUI::TextDocumentSpan> spans; 155 GUI::TextPosition position { 0, 0 }; 156 GUI::TextPosition start { 0, 0 }; 157 158 auto advance_position = [&position](char ch) { 159 if (ch == '\n') { 160 position.set_line(position.line() + 1); 161 position.set_column(0); 162 } else 163 position.set_column(position.column() + 1); 164 }; 165 166 auto append_token = [&](StringView str, const JS::Token& token, bool is_trivia) { 167 if (str.is_empty()) 168 return; 169 170 start = position; 171 for (size_t i = 0; i < str.length() - 1; ++i) 172 advance_position(str[i]); 173 174 GUI::TextDocumentSpan span; 175 span.range.set_start(start); 176 span.range.set_end({ position.line(), position.column() }); 177 auto type = is_trivia ? JS::TokenType::Invalid : token.type(); 178 auto style = style_for_token_type(palette, type); 179 span.color = style.color; 180 span.font = style.font; 181 span.is_skippable = is_trivia; 182 span.data = reinterpret_cast<void*>(static_cast<size_t>(type)); 183 spans.append(span); 184 advance_position(str[str.length() - 1]); 185 186#ifdef DEBUG_SYNTAX_HIGHLIGHTING 187 dbg() << token.name() << (is_trivia ? " (trivia) @ \"" : " @ \"") << token.value() << "\" " 188 << span.range.start().line() << ":" << span.range.start().column() << " - " 189 << span.range.end().line() << ":" << span.range.end().column(); 190#endif 191 }; 192 193 bool was_eof = false; 194 for (auto token = lexer.next(); !was_eof; token = lexer.next()) { 195 append_token(token.trivia(), token, true); 196 append_token(token.value(), token, false); 197 198 if (token.type() == JS::TokenType::Eof) 199 was_eof = true; 200 } 201 202 m_editor->document().set_spans(spans); 203 204 m_has_brace_buddies = false; 205 highlight_matching_token_pair(); 206 207 m_editor->update(); 208} 209 210Vector<SyntaxHighlighter::MatchingTokenPair> JSSyntaxHighlighter::matching_token_pairs() const 211{ 212 static Vector<SyntaxHighlighter::MatchingTokenPair> pairs; 213 if (pairs.is_empty()) { 214 pairs.append({ reinterpret_cast<void*>(JS::TokenType::CurlyOpen), reinterpret_cast<void*>(JS::TokenType::CurlyClose) }); 215 pairs.append({ reinterpret_cast<void*>(JS::TokenType::ParenOpen), reinterpret_cast<void*>(JS::TokenType::ParenClose) }); 216 pairs.append({ reinterpret_cast<void*>(JS::TokenType::BracketOpen), reinterpret_cast<void*>(JS::TokenType::BracketClose) }); 217 } 218 return pairs; 219} 220 221bool JSSyntaxHighlighter::token_types_equal(void* token1, void* token2) const 222{ 223 return static_cast<JS::TokenType>(reinterpret_cast<size_t>(token1)) == static_cast<JS::TokenType>(reinterpret_cast<size_t>(token2)); 224} 225 226JSSyntaxHighlighter::~JSSyntaxHighlighter() 227{ 228} 229 230}