From bb8e65be41154cfc8290e06d48beaea67bfce8de Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Fri, 7 Feb 2020 20:07:15 +0100 Subject: [PATCH] LibGUI+HackStudio: Move syntax highlighting from HackStudio to LibGUI This patch introduces the GUI::SyntaxHighlighter class, which can be attached to a GUI::TextEditor to provide syntax highlighting. The C++ syntax highlighting from HackStudio becomes a new class called GUI::CppSyntaxHighlighter. This will make it possible to get C++ syntax highlighting in any app that uses a GUI::TextEditor. :^) Sidenote: It does feel a bit weird having a C++ lexer in a GUI toolkit library, and we'll probably end up moving this out to a separate place as this functionality grows larger. --- DevTools/HackStudio/Editor.cpp | 96 ------------ DevTools/HackStudio/Editor.h | 12 -- DevTools/HackStudio/Makefile | 1 - DevTools/HackStudio/main.cpp | 63 +------- .../LibGUI}/CppLexer.cpp | 4 + .../LibGUI}/CppLexer.h | 4 + Libraries/LibGUI/CppSyntaxHighlighter.cpp | 147 ++++++++++++++++++ Libraries/LibGUI/CppSyntaxHighlighter.h | 16 ++ Libraries/LibGUI/Makefile | 3 + Libraries/LibGUI/SyntaxHighlighter.cpp | 37 +++++ Libraries/LibGUI/SyntaxHighlighter.h | 39 +++++ Libraries/LibGUI/TextEditor.cpp | 20 ++- Libraries/LibGUI/TextEditor.h | 5 + 13 files changed, 277 insertions(+), 170 deletions(-) rename {DevTools/HackStudio => Libraries/LibGUI}/CppLexer.cpp (99%) rename {DevTools/HackStudio => Libraries/LibGUI}/CppLexer.h (99%) create mode 100644 Libraries/LibGUI/CppSyntaxHighlighter.cpp create mode 100644 Libraries/LibGUI/CppSyntaxHighlighter.h create mode 100644 Libraries/LibGUI/SyntaxHighlighter.cpp create mode 100644 Libraries/LibGUI/SyntaxHighlighter.h diff --git a/DevTools/HackStudio/Editor.cpp b/DevTools/HackStudio/Editor.cpp index 96782b2778..9110955112 100644 --- a/DevTools/HackStudio/Editor.cpp +++ b/DevTools/HackStudio/Editor.cpp @@ -25,7 +25,6 @@ */ #include "Editor.h" -#include "CppLexer.h" #include "EditorWrapper.h" #include #include @@ -197,98 +196,3 @@ void Editor::mousemove_event(GUI::MouseEvent& event) } GUI::Application::the().hide_tooltip(); } - -void Editor::highlight_matching_token_pair() -{ - enum class Direction { - Forward, - Backward, - }; - - auto find_span_of_type = [&](int i, CppToken::Type type, CppToken::Type not_type, Direction direction) { - int nesting_level = 0; - bool forward = direction == Direction::Forward; - for (forward ? ++i : --i; forward ? (i < document().spans().size()) : (i >= 0); forward ? ++i : --i) { - auto& span = document().spans().at(i); - auto span_token_type = (CppToken::Type)((uintptr_t)span.data); - if (span_token_type == not_type) { - ++nesting_level; - } else if (span_token_type == type) { - if (nesting_level-- <= 0) - return i; - } - } - return -1; - }; - - auto make_buddies = [&](int index0, int index1) { - auto& buddy0 = const_cast(document().spans()[index0]); - auto& buddy1 = const_cast(document().spans()[index1]); - m_has_brace_buddies = true; - m_brace_buddies[0].index = index0; - m_brace_buddies[1].index = index1; - m_brace_buddies[0].span_backup = buddy0; - m_brace_buddies[1].span_backup = buddy1; - buddy0.background_color = Color::DarkCyan; - buddy1.background_color = Color::DarkCyan; - buddy0.color = Color::White; - buddy1.color = Color::White; - update(); - }; - - struct MatchingTokenPair { - CppToken::Type open; - CppToken::Type close; - }; - - MatchingTokenPair pairs[] = { - { CppToken::Type::LeftCurly, CppToken::Type::RightCurly }, - { CppToken::Type::LeftParen, CppToken::Type::RightParen }, - { CppToken::Type::LeftBracket, CppToken::Type::RightBracket }, - }; - - for (int i = 0; i < document().spans().size(); ++i) { - auto& span = const_cast(document().spans().at(i)); - auto token_type = (CppToken::Type)((uintptr_t)span.data); - - for (auto& pair : pairs) { - if (token_type == pair.open && span.range.start() == cursor()) { - auto buddy = find_span_of_type(i, pair.close, pair.open, Direction::Forward); - if (buddy != -1) - make_buddies(i, buddy); - return; - } - } - - auto right_of_end = span.range.end(); - right_of_end.set_column(right_of_end.column() + 1); - - for (auto& pair : pairs) { - if (token_type == pair.close && right_of_end == cursor()) { - auto buddy = find_span_of_type(i, pair.open, pair.close, Direction::Backward); - if (buddy != -1) - make_buddies(i, buddy); - return; - } - } - } -} - -void Editor::cursor_did_change() -{ - if (m_has_brace_buddies) { - if (m_brace_buddies[0].index >= 0 && m_brace_buddies[0].index < document().spans().size()) - document().set_span_at_index(m_brace_buddies[0].index, m_brace_buddies[0].span_backup); - if (m_brace_buddies[1].index >= 0 && m_brace_buddies[1].index < document().spans().size()) - document().set_span_at_index(m_brace_buddies[1].index, m_brace_buddies[1].span_backup); - m_has_brace_buddies = false; - update(); - } - highlight_matching_token_pair(); -} - -void Editor::notify_did_rehighlight() -{ - m_has_brace_buddies = false; - highlight_matching_token_pair(); -} diff --git a/DevTools/HackStudio/Editor.h b/DevTools/HackStudio/Editor.h index 2824f2fcaf..5bb087ed6f 100644 --- a/DevTools/HackStudio/Editor.h +++ b/DevTools/HackStudio/Editor.h @@ -41,29 +41,17 @@ public: EditorWrapper& wrapper(); const EditorWrapper& wrapper() const; - void notify_did_rehighlight(); - private: virtual void focusin_event(Core::Event&) override; virtual void focusout_event(Core::Event&) override; virtual void paint_event(GUI::PaintEvent&) override; virtual void mousemove_event(GUI::MouseEvent&) override; - virtual void cursor_did_change() override; void show_documentation_tooltip_if_available(const String&, const Gfx::Point& screen_location); - void highlight_matching_token_pair(); explicit Editor(GUI::Widget* parent); RefPtr m_documentation_tooltip_window; RefPtr m_documentation_html_view; String m_last_parsed_token; - - struct BuddySpan { - int index { -1 }; - GUI::TextDocumentSpan span_backup; - }; - - bool m_has_brace_buddies { false }; - BuddySpan m_brace_buddies[2]; }; diff --git a/DevTools/HackStudio/Makefile b/DevTools/HackStudio/Makefile index 144419e524..6598b57e35 100644 --- a/DevTools/HackStudio/Makefile +++ b/DevTools/HackStudio/Makefile @@ -6,7 +6,6 @@ OBJS = \ ProcessStateWidget.o \ FormEditorWidget.o \ FormWidget.o \ - CppLexer.o \ Editor.o \ EditorWrapper.o \ Locator.o \ diff --git a/DevTools/HackStudio/main.cpp b/DevTools/HackStudio/main.cpp index 83eba82d03..912658fa20 100644 --- a/DevTools/HackStudio/main.cpp +++ b/DevTools/HackStudio/main.cpp @@ -24,7 +24,6 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "CppLexer.h" #include "CursorTool.h" #include "Editor.h" #include "EditorWrapper.h" @@ -44,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -548,70 +548,13 @@ void run(TerminalWrapper& wrapper) wrapper.run_command("make run"); } -struct TextStyle { - Color color; - const Gfx::Font* font { nullptr }; -}; - -static TextStyle style_for_token_type(CppToken::Type type) -{ - switch (type) { - case CppToken::Type::Keyword: - return { Color::Black, &Gfx::Font::default_bold_fixed_width_font() }; - case CppToken::Type::KnownType: - return { Color::from_rgb(0x929200), &Gfx::Font::default_bold_fixed_width_font() }; - case CppToken::Type::Identifier: - return { Color::from_rgb(0x000092) }; - case CppToken::Type::DoubleQuotedString: - case CppToken::Type::SingleQuotedString: - case CppToken::Type::Number: - return { Color::from_rgb(0x920000) }; - case CppToken::Type::PreprocessorStatement: - return { Color::from_rgb(0x009292) }; - case CppToken::Type::Comment: - return { Color::from_rgb(0x009200) }; - default: - return { Color::Black }; - } -} - -static void rehighlight() -{ - auto text = current_editor().text(); - CppLexer lexer(text); - auto tokens = lexer.lex(); - - Vector spans; - for (auto& token : tokens) { -#ifdef DEBUG_SYNTAX_HIGHLIGHTING - dbg() << token.to_string() << " @ " << token.m_start.line << ":" << token.m_start.column << " - " << token.m_end.line << ":" << token.m_end.column; -#endif - GUI::TextDocumentSpan span; - span.range.set_start({ token.m_start.line, token.m_start.column }); - span.range.set_end({ token.m_end.line, token.m_end.column }); - auto style = style_for_token_type(token.m_type); - span.color = style.color; - span.font = style.font; - span.is_skippable = token.m_type == CppToken::Type::Whitespace; - span.data = (void*)token.m_type; - spans.append(span); - } - current_editor().document().set_spans(spans); - static_cast(current_editor()).notify_did_rehighlight(); - current_editor().update(); -} - void open_file(const String& filename) { auto file = g_project->get_file(filename); current_editor().set_document(const_cast(file->document())); - if (filename.ends_with(".cpp") || filename.ends_with(".h")) { - current_editor().on_change = [] { rehighlight(); }; - rehighlight(); - } else { - current_editor().on_change = nullptr; - } + if (filename.ends_with(".cpp") || filename.ends_with(".h")) + current_editor().set_syntax_highlighter(make()); if (filename.ends_with(".frm")) { set_edit_mode(EditMode::Form); diff --git a/DevTools/HackStudio/CppLexer.cpp b/Libraries/LibGUI/CppLexer.cpp similarity index 99% rename from DevTools/HackStudio/CppLexer.cpp rename to Libraries/LibGUI/CppLexer.cpp index 739b56493c..bbd76a45de 100644 --- a/DevTools/HackStudio/CppLexer.cpp +++ b/Libraries/LibGUI/CppLexer.cpp @@ -29,6 +29,8 @@ #include #include +namespace GUI { + CppLexer::CppLexer(const StringView& input) : m_input(input) { @@ -368,3 +370,5 @@ Vector CppLexer::lex() } return tokens; } + +} diff --git a/DevTools/HackStudio/CppLexer.h b/Libraries/LibGUI/CppLexer.h similarity index 99% rename from DevTools/HackStudio/CppLexer.h rename to Libraries/LibGUI/CppLexer.h index 27c1b2cc10..09e8ed8087 100644 --- a/DevTools/HackStudio/CppLexer.h +++ b/Libraries/LibGUI/CppLexer.h @@ -29,6 +29,8 @@ #include #include +namespace GUI { + #define FOR_EACH_TOKEN_TYPE \ __TOKEN(Unknown) \ __TOKEN(Whitespace) \ @@ -94,3 +96,5 @@ private: CppPosition m_previous_position { 0, 0 }; CppPosition m_position { 0, 0 }; }; + +} diff --git a/Libraries/LibGUI/CppSyntaxHighlighter.cpp b/Libraries/LibGUI/CppSyntaxHighlighter.cpp new file mode 100644 index 0000000000..5044b71832 --- /dev/null +++ b/Libraries/LibGUI/CppSyntaxHighlighter.cpp @@ -0,0 +1,147 @@ +#include +#include +#include + +namespace GUI { + +struct TextStyle { + Color color; + const Gfx::Font* font { nullptr }; +}; + +static TextStyle style_for_token_type(CppToken::Type type) +{ + switch (type) { + case CppToken::Type::Keyword: + return { Color::Black, &Gfx::Font::default_bold_fixed_width_font() }; + case CppToken::Type::KnownType: + return { Color::from_rgb(0x929200), &Gfx::Font::default_bold_fixed_width_font() }; + case CppToken::Type::Identifier: + return { Color::from_rgb(0x000092) }; + case CppToken::Type::DoubleQuotedString: + case CppToken::Type::SingleQuotedString: + case CppToken::Type::Number: + return { Color::from_rgb(0x920000) }; + case CppToken::Type::PreprocessorStatement: + return { Color::from_rgb(0x009292) }; + case CppToken::Type::Comment: + return { Color::from_rgb(0x009200) }; + default: + return { Color::Black }; + } +} + +void CppSyntaxHighlighter::rehighlight() +{ + ASSERT(m_editor); + auto text = m_editor->text(); + CppLexer lexer(text); + auto tokens = lexer.lex(); + + Vector spans; + for (auto& token : tokens) { +#ifdef DEBUG_SYNTAX_HIGHLIGHTING + dbg() << token.to_string() << " @ " << token.m_start.line << ":" << token.m_start.column << " - " << token.m_end.line << ":" << token.m_end.column; +#endif + GUI::TextDocumentSpan span; + span.range.set_start({ token.m_start.line, token.m_start.column }); + span.range.set_end({ token.m_end.line, token.m_end.column }); + auto style = style_for_token_type(token.m_type); + span.color = style.color; + span.font = style.font; + span.is_skippable = token.m_type == CppToken::Type::Whitespace; + span.data = (void*)token.m_type; + spans.append(span); + } + m_editor->document().set_spans(spans); + + m_has_brace_buddies = false; + highlight_matching_token_pair(); + + m_editor->update(); +} + +void CppSyntaxHighlighter::highlight_matching_token_pair() +{ + ASSERT(m_editor); + auto& document = m_editor->document(); + + enum class Direction { + Forward, + Backward, + }; + + auto find_span_of_type = [&](int i, CppToken::Type type, CppToken::Type not_type, Direction direction) { + int nesting_level = 0; + bool forward = direction == Direction::Forward; + for (forward ? ++i : --i; forward ? (i < document.spans().size()) : (i >= 0); forward ? ++i : --i) { + auto& span = document.spans().at(i); + auto span_token_type = (CppToken::Type)((uintptr_t)span.data); + if (span_token_type == not_type) { + ++nesting_level; + } else if (span_token_type == type) { + if (nesting_level-- <= 0) + return i; + } + } + return -1; + }; + + auto make_buddies = [&](int index0, int index1) { + auto& buddy0 = const_cast(document.spans()[index0]); + auto& buddy1 = const_cast(document.spans()[index1]); + m_has_brace_buddies = true; + m_brace_buddies[0].index = index0; + m_brace_buddies[1].index = index1; + m_brace_buddies[0].span_backup = buddy0; + m_brace_buddies[1].span_backup = buddy1; + buddy0.background_color = Color::DarkCyan; + buddy1.background_color = Color::DarkCyan; + buddy0.color = Color::White; + buddy1.color = Color::White; + m_editor->update(); + }; + + struct MatchingTokenPair { + CppToken::Type open; + CppToken::Type close; + }; + + MatchingTokenPair pairs[] = { + { CppToken::Type::LeftCurly, CppToken::Type::RightCurly }, + { CppToken::Type::LeftParen, CppToken::Type::RightParen }, + { CppToken::Type::LeftBracket, CppToken::Type::RightBracket }, + }; + + for (int i = 0; i < document.spans().size(); ++i) { + auto& span = const_cast(document.spans().at(i)); + auto token_type = (CppToken::Type)((uintptr_t)span.data); + + for (auto& pair : pairs) { + if (token_type == pair.open && span.range.start() == m_editor->cursor()) { + auto buddy = find_span_of_type(i, pair.close, pair.open, Direction::Forward); + if (buddy != -1) + make_buddies(i, buddy); + return; + } + } + + auto right_of_end = span.range.end(); + right_of_end.set_column(right_of_end.column() + 1); + + for (auto& pair : pairs) { + if (token_type == pair.close && right_of_end == m_editor->cursor()) { + auto buddy = find_span_of_type(i, pair.open, pair.close, Direction::Backward); + if (buddy != -1) + make_buddies(i, buddy); + return; + } + } + } +} + +CppSyntaxHighlighter::~CppSyntaxHighlighter() +{ +} + +} diff --git a/Libraries/LibGUI/CppSyntaxHighlighter.h b/Libraries/LibGUI/CppSyntaxHighlighter.h new file mode 100644 index 0000000000..44d048ea8f --- /dev/null +++ b/Libraries/LibGUI/CppSyntaxHighlighter.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace GUI { + +class CppSyntaxHighlighter final : public SyntaxHighlighter { +public: + CppSyntaxHighlighter() {} + + virtual ~CppSyntaxHighlighter() override; + virtual void rehighlight() override; + virtual void highlight_matching_token_pair() override; +}; + +} diff --git a/Libraries/LibGUI/Makefile b/Libraries/LibGUI/Makefile index 2b731e3ff4..3ba8f92397 100644 --- a/Libraries/LibGUI/Makefile +++ b/Libraries/LibGUI/Makefile @@ -14,6 +14,8 @@ OBJS = \ ColumnsView.o \ ComboBox.o \ Command.o \ + CppLexer.o \ + CppSyntaxHighlighter.o \ Desktop.o \ Dialog.o \ DragOperation.o \ @@ -49,6 +51,7 @@ OBJS = \ Splitter.o \ StackWidget.o \ StatusBar.o \ + SyntaxHighlighter.o \ TabWidget.o \ TableView.o \ TextBox.o \ diff --git a/Libraries/LibGUI/SyntaxHighlighter.cpp b/Libraries/LibGUI/SyntaxHighlighter.cpp new file mode 100644 index 0000000000..0e19227db0 --- /dev/null +++ b/Libraries/LibGUI/SyntaxHighlighter.cpp @@ -0,0 +1,37 @@ +#include +#include + +namespace GUI { + +SyntaxHighlighter::~SyntaxHighlighter() +{ +} + +void SyntaxHighlighter::attach(TextEditor& editor) +{ + ASSERT(!m_editor); + m_editor = editor.make_weak_ptr(); +} + +void SyntaxHighlighter::detach() +{ + ASSERT(m_editor); + m_editor = nullptr; +} + +void SyntaxHighlighter::cursor_did_change() +{ + ASSERT(m_editor); + auto& document = m_editor->document(); + if (m_has_brace_buddies) { + if (m_brace_buddies[0].index >= 0 && m_brace_buddies[0].index < document.spans().size()) + document.set_span_at_index(m_brace_buddies[0].index, m_brace_buddies[0].span_backup); + if (m_brace_buddies[1].index >= 0 && m_brace_buddies[1].index < document.spans().size()) + document.set_span_at_index(m_brace_buddies[1].index, m_brace_buddies[1].span_backup); + m_has_brace_buddies = false; + m_editor->update(); + } + highlight_matching_token_pair(); +} + +} diff --git a/Libraries/LibGUI/SyntaxHighlighter.h b/Libraries/LibGUI/SyntaxHighlighter.h new file mode 100644 index 0000000000..c7aa3dc927 --- /dev/null +++ b/Libraries/LibGUI/SyntaxHighlighter.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +namespace GUI { + +class TextEditor; + +class SyntaxHighlighter { + AK_MAKE_NONCOPYABLE(SyntaxHighlighter); + AK_MAKE_NONMOVABLE(SyntaxHighlighter); + +public: + virtual ~SyntaxHighlighter(); + + virtual void rehighlight() = 0; + virtual void highlight_matching_token_pair() = 0; + + void attach(TextEditor& editor); + void detach(); + void cursor_did_change(); + +protected: + SyntaxHighlighter() {} + + WeakPtr m_editor; + + struct BuddySpan { + int index { -1 }; + GUI::TextDocumentSpan span_backup; + }; + + bool m_has_brace_buddies { false }; + BuddySpan m_brace_buddies[2]; +}; + +} diff --git a/Libraries/LibGUI/TextEditor.cpp b/Libraries/LibGUI/TextEditor.cpp index 61e7377cd3..fbde0779c7 100644 --- a/Libraries/LibGUI/TextEditor.cpp +++ b/Libraries/LibGUI/TextEditor.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -35,8 +34,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -1030,6 +1031,8 @@ void TextEditor::set_cursor(const TextPosition& a_position) cursor_did_change(); if (on_cursor_change) on_cursor_change(); + if (m_highlighter) + m_highlighter->cursor_did_change(); } void TextEditor::focusin_event(Core::Event&) @@ -1198,6 +1201,8 @@ void TextEditor::did_change() return; if (on_change) on_change(); + if (m_highlighter) + m_highlighter->rehighlight(); m_has_pending_change_notification = false; }); } @@ -1476,7 +1481,20 @@ void TextEditor::flush_pending_change_notification_if_needed() return; if (on_change) on_change(); + if (m_highlighter) + m_highlighter->rehighlight(); m_has_pending_change_notification = false; } +void TextEditor::set_syntax_highlighter(OwnPtr highlighter) +{ + if (m_highlighter) + m_highlighter->detach(); + m_highlighter = move(highlighter); + if (m_highlighter) { + m_highlighter->attach(*this); + m_highlighter->rehighlight(); + } +} + } diff --git a/Libraries/LibGUI/TextEditor.h b/Libraries/LibGUI/TextEditor.h index 88a0a6a762..be041091a1 100644 --- a/Libraries/LibGUI/TextEditor.h +++ b/Libraries/LibGUI/TextEditor.h @@ -42,6 +42,7 @@ class Action; class Menu; class Painter; class ScrollBar; +class SyntaxHighlighter; class TextEditor : public ScrollableWidget @@ -133,6 +134,8 @@ public: void set_cursor(size_t line, size_t column); void set_cursor(const TextPosition&); + void set_syntax_highlighter(OwnPtr); + protected: explicit TextEditor(Widget* parent); explicit TextEditor(Type, Widget* parent); @@ -248,6 +251,8 @@ private: }; NonnullOwnPtrVector m_line_visual_data; + + OwnPtr m_highlighter; }; }