summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOriko <oriko1010@protonmail.com>2020-03-13 00:52:03 +0200
committerAndreas Kling <kling@serenityos.org>2020-03-13 22:53:13 +0100
commit196352ef671385c2491cf2fc41abb18c7f2c48fc (patch)
treefee19820801d4e3015debf209f8679e2290ae72d
parentc02037e944713d31d1cc113d8d261dfbcfe3e5ce (diff)
downloadserenity-196352ef671385c2491cf2fc41abb18c7f2c48fc.zip
LibGUI: Add Javascript syntax highlighter
-rw-r--r--Libraries/LibGUI/JSSyntaxHighlighter.cpp157
-rw-r--r--Libraries/LibGUI/JSSyntaxHighlighter.h23
-rw-r--r--Libraries/LibGUI/Makefile1
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 \