diff options
author | Oriko <oriko1010@protonmail.com> | 2020-03-13 00:52:03 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-03-13 22:53:13 +0100 |
commit | 196352ef671385c2491cf2fc41abb18c7f2c48fc (patch) | |
tree | fee19820801d4e3015debf209f8679e2290ae72d | |
parent | c02037e944713d31d1cc113d8d261dfbcfe3e5ce (diff) | |
download | serenity-196352ef671385c2491cf2fc41abb18c7f2c48fc.zip |
LibGUI: Add Javascript syntax highlighter
-rw-r--r-- | Libraries/LibGUI/JSSyntaxHighlighter.cpp | 157 | ||||
-rw-r--r-- | Libraries/LibGUI/JSSyntaxHighlighter.h | 23 | ||||
-rw-r--r-- | Libraries/LibGUI/Makefile | 1 |
3 files changed, 181 insertions, 0 deletions
diff --git a/Libraries/LibGUI/JSSyntaxHighlighter.cpp b/Libraries/LibGUI/JSSyntaxHighlighter.cpp new file mode 100644 index 0000000000..e5296f2644 --- /dev/null +++ b/Libraries/LibGUI/JSSyntaxHighlighter.cpp @@ -0,0 +1,157 @@ +#include <LibGUI/JSSyntaxHighlighter.h> +#include <LibGUI/TextEditor.h> +#include <LibGfx/Font.h> +#include <LibJS/Lexer.h> +#include <LibJS/Token.h> + +namespace GUI { + +static TextStyle style_for_token_type(JS::TokenType type) +{ + switch (type) { + case JS::TokenType::BoolLiteral: + case JS::TokenType::NullLiteral: + return { Color::Black, &Gfx::Font::default_bold_fixed_width_font() }; + case JS::TokenType::Catch: + case JS::TokenType::Class: + case JS::TokenType::Const: + case JS::TokenType::Delete: + case JS::TokenType::Do: + case JS::TokenType::Else: + case JS::TokenType::Finally: + case JS::TokenType::For: + case JS::TokenType::Function: + case JS::TokenType::If: + case JS::TokenType::Interface: + case JS::TokenType::Let: + case JS::TokenType::New: + case JS::TokenType::QuestionMark: + case JS::TokenType::Return: + case JS::TokenType::Try: + case JS::TokenType::Var: + case JS::TokenType::While: + return { Color::Black, &Gfx::Font::default_bold_fixed_width_font() }; + case JS::TokenType::Identifier: + return { Color::from_rgb(0x092e64) }; + case JS::TokenType::NumericLiteral: + case JS::TokenType::StringLiteral: + case JS::TokenType::RegexLiteral: + return { Color::from_rgb(0x800000) }; + case JS::TokenType::Invalid: + case JS::TokenType::Eof: + return { Color::from_rgb(0x008000) }; + default: + return { Color::Black }; + } +} + +bool JSSyntaxHighlighter::is_identifier(void* token) const +{ + auto js_token = static_cast<JS::TokenType>(reinterpret_cast<size_t>(token)); + return js_token == JS::TokenType::Identifier; +} + +bool JSSyntaxHighlighter::is_navigatable(void* token) const +{ + (void)token; + return false; +} + +void JSSyntaxHighlighter::rehighlight() +{ + ASSERT(m_editor); + auto text = m_editor->text(); + + JS::Lexer lexer(text); + + Vector<GUI::TextDocumentSpan> spans; + GUI::TextPosition position { 0, 0 }; + GUI::TextPosition start { 0, 0 }; + + auto advance_position = [&](char ch) { + if (ch == '\n') { + position.set_line(position.line() + 1); + position.set_column(0); + } else + position.set_column(position.column() + 1); + }; + + bool was_eof = false; + for (auto token = lexer.next(); !was_eof; token = lexer.next()) { + start = position; + if (!token.trivia().is_empty()) { + for (auto ch : token.trivia()) + advance_position(ch); + + GUI::TextDocumentSpan span; + + span.range.set_start(start); + if (position.column() > 0) + span.range.set_end({ position.line(), position.column() - 1 }); + else + span.range.set_end({ position.line() - 1, 0 }); + auto style = style_for_token_type(JS::TokenType::Invalid); + span.color = style.color; + span.font = style.font; + span.is_skippable = true; + spans.append(span); +#ifdef DEBUG_SYNTAX_HIGHLIGHTING + dbg() << token.name() << " \"" << token.trivia() << "\" (trivia) @ " << span.range.start().line() << ":" << span.range.start().column() << " - " << span.range.end().line() << ":" << span.range.end().column(); +#endif + } + + start = position; + if (!token.value().is_empty()) { + for (auto ch : token.value()) + advance_position(ch); + + GUI::TextDocumentSpan span; + span.range.set_start(start); + if (position.column() > 0) + span.range.set_end({ position.line(), position.column() - 1 }); + else + span.range.set_end({ position.line() - 1, 0 }); + auto style = style_for_token_type(token.type()); + span.color = style.color; + span.font = style.font; + span.is_skippable = false; + span.data = reinterpret_cast<void*>(token.type()); + spans.append(span); +#ifdef DEBUG_SYNTAX_HIGHLIGHTING + dbg() << token.name() << " @ \"" << token.value() << "\" " << span.range.start().line() << ":" << span.range.start().column() << " - " << span.range.end().line() << ":" << span.range.end().column(); +#endif + } + + if (token.type() == JS::TokenType::Eof) + was_eof = true; + } + + m_editor->document().set_spans(spans); + + m_has_brace_buddies = false; + highlight_matching_token_pair(); + + m_editor->update(); +} + +Vector<SyntaxHighlighter::MatchingTokenPair> JSSyntaxHighlighter::matching_token_pairs() const +{ + static Vector<SyntaxHighlighter::MatchingTokenPair> pairs; + if (pairs.is_empty()) { + pairs.append({ reinterpret_cast<void*>(JS::TokenType::CurlyOpen), reinterpret_cast<void*>(JS::TokenType::CurlyClose) }); + pairs.append({ reinterpret_cast<void*>(JS::TokenType::ParenOpen), reinterpret_cast<void*>(JS::TokenType::ParenClose) }); + pairs.append({ reinterpret_cast<void*>(JS::TokenType::BracketOpen), reinterpret_cast<void*>(JS::TokenType::BracketClose) }); + } + return pairs; +} + +bool JSSyntaxHighlighter::token_types_equal(void* token1, void* token2) const +{ + return static_cast<JS::TokenType>(reinterpret_cast<size_t>(token1)) == static_cast<JS::TokenType>(reinterpret_cast<size_t>(token2)); +} + +JSSyntaxHighlighter::~JSSyntaxHighlighter() +{ +} + +} diff --git a/Libraries/LibGUI/JSSyntaxHighlighter.h b/Libraries/LibGUI/JSSyntaxHighlighter.h new file mode 100644 index 0000000000..dcfec5ebbe --- /dev/null +++ b/Libraries/LibGUI/JSSyntaxHighlighter.h @@ -0,0 +1,23 @@ +#pragma once + +#include <LibGUI/SyntaxHighlighter.h> + +namespace GUI { + +class JSSyntaxHighlighter final : public SyntaxHighlighter { +public: + JSSyntaxHighlighter() {} + virtual ~JSSyntaxHighlighter() override; + + virtual bool is_identifier(void*) const override; + virtual bool is_navigatable(void*) const override; + + virtual SyntaxLanguage language() const override { return SyntaxLanguage::Javascript; } + virtual void rehighlight() override; + +protected: + virtual Vector<MatchingTokenPair> matching_token_pairs() const override; + virtual bool token_types_equal(void*, void*) const override; +}; + +} diff --git a/Libraries/LibGUI/Makefile b/Libraries/LibGUI/Makefile index dc1bc685b8..648b970282 100644 --- a/Libraries/LibGUI/Makefile +++ b/Libraries/LibGUI/Makefile @@ -29,6 +29,7 @@ OBJS = \ InputBox.o \ ItemView.o \ JsonArrayModel.o \ + JSSyntaxHighlighter.o \ Label.o \ Layout.o \ LazyWidget.o \ |