Serenity Operating System
at master 174 lines 8.9 kB view raw
1/* 2 * Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Debug.h> 8#include <LibWeb/CSS/Parser/Tokenizer.h> 9#include <LibWeb/CSS/SyntaxHighlighter/SyntaxHighlighter.h> 10 11namespace Web::CSS { 12 13bool SyntaxHighlighter::is_identifier(u64 token) const 14{ 15 return static_cast<CSS::Parser::Token::Type>(token) == CSS::Parser::Token::Type::Ident; 16} 17 18bool SyntaxHighlighter::is_navigatable(u64) const 19{ 20 return false; 21} 22 23void SyntaxHighlighter::rehighlight(Palette const& palette) 24{ 25 dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(CSS::SyntaxHighlighter) starting rehighlight"); 26 auto text = m_client->get_text(); 27 28 Vector<Parser::Token> folding_region_start_tokens; 29 Vector<GUI::TextDocumentFoldingRegion> folding_regions; 30 Vector<GUI::TextDocumentSpan> spans; 31 32 auto highlight = [&](auto start_line, auto start_column, auto end_line, auto end_column, Gfx::TextAttributes attributes, CSS::Parser::Token::Type type) { 33 if (start_line > end_line || (start_line == end_line && start_column >= end_column)) { 34 dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(CSS::SyntaxHighlighter) discarding ({}-{}) to ({}-{}) because it has zero or negative length", start_line, start_column, end_line, end_column); 35 return; 36 } 37 dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(CSS::SyntaxHighlighter) highlighting ({}-{}) to ({}-{}) with color {}", start_line, start_column, end_line, end_column, attributes.color); 38 spans.empend( 39 GUI::TextRange { 40 { start_line, start_column }, 41 { end_line, end_column }, 42 }, 43 move(attributes), 44 static_cast<u64>(type), 45 false); 46 }; 47 48 auto tokens = CSS::Parser::Tokenizer::tokenize(text, "utf-8"sv).release_value_but_fixme_should_propagate_errors(); 49 for (auto const& token : tokens) { 50 if (token.is(Parser::Token::Type::EndOfFile)) 51 break; 52 53 if (token.is(Parser::Token::Type::OpenCurly)) { 54 folding_region_start_tokens.append(token); 55 } else if (token.is(Parser::Token::Type::CloseCurly)) { 56 if (!folding_region_start_tokens.is_empty()) { 57 auto start_token = folding_region_start_tokens.take_last(); 58 GUI::TextDocumentFoldingRegion folding_region; 59 folding_region.range.set_start({ start_token.end_position().line, start_token.end_position().column }); 60 folding_region.range.set_end({ token.start_position().line, token.start_position().column }); 61 folding_regions.append(move(folding_region)); 62 } 63 } 64 65 switch (token.type()) { 66 case Parser::Token::Type::Ident: 67 highlight(token.start_position().line, token.start_position().column, token.end_position().line, token.end_position().column, { palette.syntax_identifier(), {} }, token.type()); 68 break; 69 70 case Parser::Token::Type::String: 71 highlight(token.start_position().line, token.start_position().column, token.end_position().line, token.end_position().column, { palette.syntax_string(), {} }, token.type()); 72 break; 73 74 case Parser::Token::Type::Whitespace: 75 // CSS doesn't produce comment tokens, they're just included as part of whitespace. 76 highlight(token.start_position().line, token.start_position().column, token.end_position().line, token.end_position().column, { palette.syntax_comment(), {} }, token.type()); 77 break; 78 79 case Parser::Token::Type::AtKeyword: 80 highlight(token.start_position().line, token.start_position().column, token.end_position().line, token.end_position().column, { palette.syntax_keyword(), {} }, token.type()); 81 break; 82 83 case Parser::Token::Type::Function: 84 // Function tokens include the opening '(', so we split that into two tokens for highlighting purposes. 85 highlight(token.start_position().line, token.start_position().column, token.end_position().line, token.end_position().column - 1, { palette.syntax_keyword(), {} }, token.type()); 86 highlight(token.end_position().line, token.end_position().column - 1, token.end_position().line, token.end_position().column, { palette.syntax_punctuation(), {} }, Parser::Token::Type::OpenParen); 87 break; 88 89 case Parser::Token::Type::Url: 90 // An Url token is a `url()` function with its parameter string unquoted. 91 // url 92 highlight(token.start_position().line, token.start_position().column, token.start_position().line, token.start_position().column + 3, { palette.syntax_keyword(), {} }, token.type()); 93 // ( 94 highlight(token.start_position().line, token.start_position().column + 3, token.start_position().line, token.start_position().column + 4, { palette.syntax_punctuation(), {} }, Parser::Token::Type::OpenParen); 95 // <string> 96 highlight(token.start_position().line, token.start_position().column + 4, token.end_position().line, token.end_position().column - 1, { palette.syntax_string(), {} }, Parser::Token::Type::String); 97 // ) 98 highlight(token.end_position().line, token.end_position().column - 1, token.end_position().line, token.end_position().column, { palette.syntax_punctuation(), {} }, Parser::Token::Type::CloseParen); 99 break; 100 101 case Parser::Token::Type::Number: 102 case Parser::Token::Type::Dimension: 103 case Parser::Token::Type::Percentage: 104 highlight(token.start_position().line, token.start_position().column, token.end_position().line, token.end_position().column, { palette.syntax_number(), {} }, token.type()); 105 break; 106 107 case Parser::Token::Type::Delim: 108 case Parser::Token::Type::Colon: 109 case Parser::Token::Type::Comma: 110 case Parser::Token::Type::Semicolon: 111 case Parser::Token::Type::OpenCurly: 112 case Parser::Token::Type::OpenParen: 113 case Parser::Token::Type::OpenSquare: 114 case Parser::Token::Type::CloseCurly: 115 case Parser::Token::Type::CloseParen: 116 case Parser::Token::Type::CloseSquare: 117 highlight(token.start_position().line, token.start_position().column, token.end_position().line, token.end_position().column, { palette.syntax_punctuation(), {} }, token.type()); 118 break; 119 120 case Parser::Token::Type::CDO: 121 case Parser::Token::Type::CDC: 122 highlight(token.start_position().line, token.start_position().column, token.end_position().line, token.end_position().column, { palette.syntax_comment(), {} }, token.type()); 123 break; 124 125 case Parser::Token::Type::Hash: 126 // FIXME: Hash tokens can be ID selectors or colors, we don't know which without parsing properly. 127 highlight(token.start_position().line, token.start_position().column, token.end_position().line, token.end_position().column, { palette.syntax_number(), {} }, token.type()); 128 break; 129 130 case Parser::Token::Type::Invalid: 131 case Parser::Token::Type::BadUrl: 132 case Parser::Token::Type::BadString: 133 // FIXME: Error highlighting color in palette? 134 highlight(token.start_position().line, token.start_position().column, token.end_position().line, token.end_position().column, { Color(Color::NamedColor::Red), {}, false, true }, token.type()); 135 break; 136 137 case Parser::Token::Type::EndOfFile: 138 default: 139 break; 140 } 141 } 142 143 if constexpr (SYNTAX_HIGHLIGHTING_DEBUG) { 144 dbgln("(CSS::SyntaxHighlighter) list of all spans:"); 145 for (auto& span : spans) 146 dbgln("{}, {} - {}", span.range, span.attributes.color, span.data); 147 dbgln("(CSS::SyntaxHighlighter) end of list"); 148 } 149 150 m_client->do_set_spans(move(spans)); 151 m_client->do_set_folding_regions(move(folding_regions)); 152 m_has_brace_buddies = false; 153 highlight_matching_token_pair(); 154 m_client->do_update(); 155} 156 157Vector<Syntax::Highlighter::MatchingTokenPair> SyntaxHighlighter::matching_token_pairs_impl() const 158{ 159 static Vector<Syntax::Highlighter::MatchingTokenPair> pairs; 160 if (pairs.is_empty()) { 161 pairs.append({ static_cast<u64>(CSS::Parser::Token::Type::OpenCurly), static_cast<u64>(CSS::Parser::Token::Type::CloseCurly) }); 162 pairs.append({ static_cast<u64>(CSS::Parser::Token::Type::OpenParen), static_cast<u64>(CSS::Parser::Token::Type::CloseParen) }); 163 pairs.append({ static_cast<u64>(CSS::Parser::Token::Type::OpenSquare), static_cast<u64>(CSS::Parser::Token::Type::CloseSquare) }); 164 pairs.append({ static_cast<u64>(CSS::Parser::Token::Type::CDO), static_cast<u64>(CSS::Parser::Token::Type::CDC) }); 165 } 166 return pairs; 167} 168 169bool SyntaxHighlighter::token_types_equal(u64 token0, u64 token1) const 170{ 171 return token0 == token1; 172} 173 174}