mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 17:02:45 +00:00 
			
		
		
		
	HackStudio+LibGUI: Implement matching curly brace highlighting
This works for C++ syntax highlighted text documents by caching the C++
token type in a new "arbitrary data" member of GTextDocumentSpan.
When the cursor is placed immediately before a '{' or immediately after
a '}', we highlight both of these brace buddies by changing their
corresponding spans to have a different background color.
..and spans can also now have a custom background color. :^)
			
			
This commit is contained in:
		
							parent
							
								
									5f7f97355e
								
							
						
					
					
						commit
						c8e02e60a6
					
				
					 6 changed files with 89 additions and 1 deletions
				
			
		|  | @ -1,4 +1,5 @@ | ||||||
| #include "Editor.h" | #include "Editor.h" | ||||||
|  | #include "CppLexer.h" | ||||||
| #include "EditorWrapper.h" | #include "EditorWrapper.h" | ||||||
| #include <AK/FileSystemPath.h> | #include <AK/FileSystemPath.h> | ||||||
| #include <LibCore/CDirIterator.h> | #include <LibCore/CDirIterator.h> | ||||||
|  | @ -170,3 +171,71 @@ void Editor::mousemove_event(GMouseEvent& event) | ||||||
|     } |     } | ||||||
|     GApplication::the().hide_tooltip(); |     GApplication::the().hide_tooltip(); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | 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(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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<GTextDocumentSpan&>(document().spans()[index0]); | ||||||
|  |         auto& buddy1 = const_cast<GTextDocumentSpan&>(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(); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     for (int i = 0; i < document().spans().size(); ++i) { | ||||||
|  |         auto& span = const_cast<GTextDocumentSpan&>(document().spans().at(i)); | ||||||
|  |         auto token_type = (CppToken::Type)((uintptr_t)span.data); | ||||||
|  |         if (token_type == CppToken::Type::LeftCurly && span.range.start() == cursor()) { | ||||||
|  |             auto buddy = find_span_of_type(i, CppToken::Type::RightCurly, CppToken::Type::LeftCurly, 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); | ||||||
|  |         if (token_type == CppToken::Type::RightCurly && right_of_end == cursor()) { | ||||||
|  |             auto buddy = find_span_of_type(i, CppToken::Type::LeftCurly, CppToken::Type::RightCurly, Direction::Backward); | ||||||
|  |             if (buddy != -1) | ||||||
|  |                 make_buddies(i, buddy); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ private: | ||||||
|     virtual void focusout_event(CEvent&) override; |     virtual void focusout_event(CEvent&) override; | ||||||
|     virtual void paint_event(GPaintEvent&) override; |     virtual void paint_event(GPaintEvent&) override; | ||||||
|     virtual void mousemove_event(GMouseEvent&) override; |     virtual void mousemove_event(GMouseEvent&) override; | ||||||
|  |     virtual void cursor_did_change() override; | ||||||
| 
 | 
 | ||||||
|     void show_documentation_tooltip_if_available(const String&, const Point& screen_location); |     void show_documentation_tooltip_if_available(const String&, const Point& screen_location); | ||||||
| 
 | 
 | ||||||
|  | @ -28,4 +29,12 @@ private: | ||||||
|     RefPtr<GWindow> m_documentation_tooltip_window; |     RefPtr<GWindow> m_documentation_tooltip_window; | ||||||
|     RefPtr<HtmlView> m_documentation_html_view; |     RefPtr<HtmlView> m_documentation_html_view; | ||||||
|     String m_last_parsed_token; |     String m_last_parsed_token; | ||||||
|  | 
 | ||||||
|  |     struct BuddySpan { | ||||||
|  |         int index { -1 }; | ||||||
|  |         GTextDocumentSpan span_backup; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     bool m_has_brace_buddies { false }; | ||||||
|  |     BuddySpan m_brace_buddies[2]; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -423,7 +423,7 @@ int main(int argc, char** argv) | ||||||
|         remove_current_editor_action->set_enabled(g_all_editor_wrappers.size() > 1); |         remove_current_editor_action->set_enabled(g_all_editor_wrappers.size() > 1); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     open_file("test.frm"); |     open_file("main.cpp"); | ||||||
| 
 | 
 | ||||||
|     update_actions(); |     update_actions(); | ||||||
|     return app.exec(); |     return app.exec(); | ||||||
|  | @ -484,6 +484,7 @@ static void rehighlight() | ||||||
|         span.color = style.color; |         span.color = style.color; | ||||||
|         span.font = style.font; |         span.font = style.font; | ||||||
|         span.is_skippable = token.m_type == CppToken::Type::Whitespace; |         span.is_skippable = token.m_type == CppToken::Type::Whitespace; | ||||||
|  |         span.data = (void*)token.m_type; | ||||||
|         spans.append(span); |         spans.append(span); | ||||||
|     } |     } | ||||||
|     current_editor().document().set_spans(spans); |     current_editor().document().set_spans(spans); | ||||||
|  |  | ||||||
|  | @ -15,8 +15,10 @@ class GTextDocumentLine; | ||||||
| struct GTextDocumentSpan { | struct GTextDocumentSpan { | ||||||
|     GTextRange range; |     GTextRange range; | ||||||
|     Color color; |     Color color; | ||||||
|  |     Optional<Color> background_color; | ||||||
|     bool is_skippable { false }; |     bool is_skippable { false }; | ||||||
|     const Font* font { nullptr }; |     const Font* font { nullptr }; | ||||||
|  |     void* data { nullptr }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class GTextDocument : public RefCounted<GTextDocument> { | class GTextDocument : public RefCounted<GTextDocument> { | ||||||
|  | @ -54,6 +56,7 @@ public: | ||||||
| 
 | 
 | ||||||
|     bool has_spans() const { return !m_spans.is_empty(); } |     bool has_spans() const { return !m_spans.is_empty(); } | ||||||
|     const Vector<GTextDocumentSpan>& spans() const { return m_spans; } |     const Vector<GTextDocumentSpan>& spans() const { return m_spans; } | ||||||
|  |     void set_span_at_index(int index, GTextDocumentSpan span) { m_spans[index] = move(span); } | ||||||
| 
 | 
 | ||||||
|     void append_line(NonnullOwnPtr<GTextDocumentLine>); |     void append_line(NonnullOwnPtr<GTextDocumentLine>); | ||||||
|     void remove_line(int line_index); |     void remove_line(int line_index); | ||||||
|  |  | ||||||
|  | @ -389,6 +389,7 @@ void GTextEditor::paint_event(GPaintEvent& event) | ||||||
|                 for (int i = 0; i < visual_line_text.length(); ++i) { |                 for (int i = 0; i < visual_line_text.length(); ++i) { | ||||||
|                     const Font* font = &this->font(); |                     const Font* font = &this->font(); | ||||||
|                     Color color; |                     Color color; | ||||||
|  |                     Optional<Color> background_color; | ||||||
|                     GTextPosition physical_position(line_index, start_of_visual_line + i); |                     GTextPosition physical_position(line_index, start_of_visual_line + i); | ||||||
|                     // FIXME: This is *horribly* inefficient.
 |                     // FIXME: This is *horribly* inefficient.
 | ||||||
|                     for (auto& span : document().spans()) { |                     for (auto& span : document().spans()) { | ||||||
|  | @ -397,8 +398,11 @@ void GTextEditor::paint_event(GPaintEvent& event) | ||||||
|                         color = span.color; |                         color = span.color; | ||||||
|                         if (span.font) |                         if (span.font) | ||||||
|                             font = span.font; |                             font = span.font; | ||||||
|  |                         background_color = span.background_color; | ||||||
|                         break; |                         break; | ||||||
|                     } |                     } | ||||||
|  |                     if (background_color.has_value()) | ||||||
|  |                         painter.fill_rect(character_rect, background_color.value()); | ||||||
|                     painter.draw_text(character_rect, visual_line_text.substring_view(i, 1), *font, m_text_alignment, color); |                     painter.draw_text(character_rect, visual_line_text.substring_view(i, 1), *font, m_text_alignment, color); | ||||||
|                     character_rect.move_by(advance, 0); |                     character_rect.move_by(advance, 0); | ||||||
|                 } |                 } | ||||||
|  | @ -1135,6 +1139,7 @@ void GTextEditor::set_cursor(const GTextPosition& a_position) | ||||||
|         update(old_cursor_line_rect); |         update(old_cursor_line_rect); | ||||||
|         update_cursor(); |         update_cursor(); | ||||||
|     } |     } | ||||||
|  |     cursor_did_change(); | ||||||
|     if (on_cursor_change) |     if (on_cursor_change) | ||||||
|         on_cursor_change(); |         on_cursor_change(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -120,6 +120,7 @@ protected: | ||||||
|     virtual void leave_event(CEvent&) override; |     virtual void leave_event(CEvent&) override; | ||||||
|     virtual void context_menu_event(GContextMenuEvent&) override; |     virtual void context_menu_event(GContextMenuEvent&) override; | ||||||
|     virtual void resize_event(GResizeEvent&) override; |     virtual void resize_event(GResizeEvent&) override; | ||||||
|  |     virtual void cursor_did_change() {} | ||||||
| 
 | 
 | ||||||
|     GTextPosition text_position_at(const Point&) const; |     GTextPosition text_position_at(const Point&) const; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling