Browse Source

LibWebView: Highlight CSS and JS in view-source

The colors and categories here could probably be better, but it works.
:^)
Sam Atkins 10 months ago
parent
commit
bd6fdbf312

+ 117 - 16
Userland/Libraries/LibWebView/SourceHighlighter.cpp

@@ -6,7 +6,11 @@
  */
 
 #include <AK/StringBuilder.h>
+#include <LibJS/SyntaxHighlighter.h>
+#include <LibJS/Token.h>
 #include <LibURL/URL.h>
+#include <LibWeb/CSS/Parser/Token.h>
+#include <LibWeb/CSS/SyntaxHighlighter/SyntaxHighlighter.h>
 #include <LibWeb/HTML/SyntaxHighlighter/SyntaxHighlighter.h>
 #include <LibWebView/SourceHighlighter.h>
 
@@ -40,9 +44,15 @@ SourceHighlighterClient::SourceHighlighterClient(StringView source, Syntax::Lang
     Gfx::Palette dummy_palette { palette_impl };
 
     switch (language) {
+    case Syntax::Language::CSS:
+        m_highlighter = make<Web::CSS::SyntaxHighlighter>();
+        break;
     case Syntax::Language::HTML:
         m_highlighter = make<Web::HTML::SyntaxHighlighter>();
         break;
+    case Syntax::Language::JavaScript:
+        m_highlighter = make<JS::SyntaxHighlighter>();
+        break;
     default:
         break;
     }
@@ -111,24 +121,115 @@ String highlight_source(URL::URL const& url, StringView source)
 
 StringView SourceHighlighterClient::class_for_token(u64 token_type) const
 {
-    switch (static_cast<Web::HTML::AugmentedTokenKind>(token_type)) {
-    case Web::HTML::AugmentedTokenKind::AttributeName:
-        return "attribute-name"sv;
-    case Web::HTML::AugmentedTokenKind::AttributeValue:
-        return "attribute-value"sv;
-    case Web::HTML::AugmentedTokenKind::OpenTag:
-    case Web::HTML::AugmentedTokenKind::CloseTag:
-        return "tag"sv;
-    case Web::HTML::AugmentedTokenKind::Comment:
-        return "comment"sv;
-    case Web::HTML::AugmentedTokenKind::Doctype:
-        return "doctype"sv;
-    case Web::HTML::AugmentedTokenKind::__Count:
+    auto class_for_css_token = [](u64 token_type) {
+        switch (static_cast<Web::CSS::Parser::Token::Type>(token_type)) {
+        case Web::CSS::Parser::Token::Type::Invalid:
+        case Web::CSS::Parser::Token::Type::BadString:
+        case Web::CSS::Parser::Token::Type::BadUrl:
+            return "invalid"sv;
+        case Web::CSS::Parser::Token::Type::Ident:
+            return "identifier"sv;
+        case Web::CSS::Parser::Token::Type::Function:
+            return "function"sv;
+        case Web::CSS::Parser::Token::Type::AtKeyword:
+            return "at-keyword"sv;
+        case Web::CSS::Parser::Token::Type::Hash:
+            return "hash"sv;
+        case Web::CSS::Parser::Token::Type::String:
+            return "string"sv;
+        case Web::CSS::Parser::Token::Type::Url:
+            return "url"sv;
+        case Web::CSS::Parser::Token::Type::Number:
+        case Web::CSS::Parser::Token::Type::Dimension:
+        case Web::CSS::Parser::Token::Type::Percentage:
+            return "number"sv;
+        case Web::CSS::Parser::Token::Type::Whitespace:
+            return "whitespace"sv;
+        case Web::CSS::Parser::Token::Type::Delim:
+        case Web::CSS::Parser::Token::Type::Colon:
+        case Web::CSS::Parser::Token::Type::Semicolon:
+        case Web::CSS::Parser::Token::Type::Comma:
+        case Web::CSS::Parser::Token::Type::OpenSquare:
+        case Web::CSS::Parser::Token::Type::CloseSquare:
+        case Web::CSS::Parser::Token::Type::OpenParen:
+        case Web::CSS::Parser::Token::Type::CloseParen:
+        case Web::CSS::Parser::Token::Type::OpenCurly:
+        case Web::CSS::Parser::Token::Type::CloseCurly:
+            return "delimiter"sv;
+        case Web::CSS::Parser::Token::Type::CDO:
+        case Web::CSS::Parser::Token::Type::CDC:
+            return "comment"sv;
+        case Web::CSS::Parser::Token::Type::EndOfFile:
+        default:
+            break;
+        }
+        return ""sv;
+    };
+
+    auto class_for_js_token = [](u64 token_type) {
+        auto category = JS::Token::category(static_cast<JS::TokenType>(token_type));
+        switch (category) {
+        case JS::TokenCategory::Invalid:
+            return "invalid"sv;
+        case JS::TokenCategory::Number:
+            return "number"sv;
+        case JS::TokenCategory::String:
+            return "string"sv;
+        case JS::TokenCategory::Punctuation:
+            return "punctuation"sv;
+        case JS::TokenCategory::Operator:
+            return "operator"sv;
+        case JS::TokenCategory::Keyword:
+            return "keyword"sv;
+        case JS::TokenCategory::ControlKeyword:
+            return "control-keyword"sv;
+        case JS::TokenCategory::Identifier:
+            return "identifier"sv;
+        default:
+            break;
+        }
+        return ""sv;
+    };
+
+    switch (m_highlighter->language()) {
+    case Syntax::Language::CSS:
+        return class_for_css_token(token_type);
+    case Syntax::Language::JavaScript:
+        return class_for_js_token(token_type);
+    case Syntax::Language::HTML: {
+        // HTML has nested CSS and JS highlighters, so we have to decode their token types.
+
+        // HTML
+        if (token_type < Web::HTML::SyntaxHighlighter::JS_TOKEN_START_VALUE) {
+            switch (static_cast<Web::HTML::AugmentedTokenKind>(token_type)) {
+            case Web::HTML::AugmentedTokenKind::AttributeName:
+                return "attribute-name"sv;
+            case Web::HTML::AugmentedTokenKind::AttributeValue:
+                return "attribute-value"sv;
+            case Web::HTML::AugmentedTokenKind::OpenTag:
+            case Web::HTML::AugmentedTokenKind::CloseTag:
+                return "tag"sv;
+            case Web::HTML::AugmentedTokenKind::Comment:
+                return "comment"sv;
+            case Web::HTML::AugmentedTokenKind::Doctype:
+                return "doctype"sv;
+            case Web::HTML::AugmentedTokenKind::__Count:
+            default:
+                return ""sv;
+            }
+        }
+
+        // JS
+        if (token_type < Web::HTML::SyntaxHighlighter::CSS_TOKEN_START_VALUE) {
+            return class_for_js_token(token_type - Web::HTML::SyntaxHighlighter::JS_TOKEN_START_VALUE);
+        }
+
+        // CSS
+        return class_for_css_token(token_type - Web::HTML::SyntaxHighlighter::CSS_TOKEN_START_VALUE);
+    }
     default:
-        break;
+        return "unknown"sv;
     }
-
-    return "unknown"sv;
 }
 
 static String generate_style()

+ 17 - 0
Userland/Libraries/LibWebView/SourceHighlighter.h

@@ -84,6 +84,8 @@ constexpr inline StringView HTML_HIGHLIGHTER_STYLE = R"~~~(
             --name-color: orange;
             --value-color: deepskyblue;
             --internal-color: darkgrey;
+            --string-color: goldenrod;
+            --error-color: red;
         }
     }
 
@@ -94,6 +96,8 @@ constexpr inline StringView HTML_HIGHLIGHTER_STYLE = R"~~~(
             --name-color: darkorange;
             --value-color: blue;
             --internal-color: dimgrey;
+            --string-color: darkgoldenrod;
+            --error-color: darkred;
         }
     }
 
@@ -118,6 +122,19 @@ constexpr inline StringView HTML_HIGHLIGHTER_STYLE = R"~~~(
     .internal {
         color: var(--internal-color);
     }
+    .invalid {
+        color: var(--error-color);
+        text-decoration: currentColor wavy underline;
+    }
+    .at-keyword, .function, .keyword, .control-keyword, .url {
+        color: var(--keyword-color);
+    }
+    .number, .hash {
+        color: var(--value-color);
+    }
+    .string {
+        color: var(--string-color);
+    }
 )~~~"sv;
 
 }