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 "CppLexer.h" | ||||
| #include "EditorWrapper.h" | ||||
| #include <AK/FileSystemPath.h> | ||||
| #include <LibCore/CDirIterator.h> | ||||
|  | @ -170,3 +171,71 @@ void Editor::mousemove_event(GMouseEvent& event) | |||
|     } | ||||
|     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 paint_event(GPaintEvent&) 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); | ||||
| 
 | ||||
|  | @ -28,4 +29,12 @@ private: | |||
|     RefPtr<GWindow> m_documentation_tooltip_window; | ||||
|     RefPtr<HtmlView> m_documentation_html_view; | ||||
|     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); | ||||
|     }; | ||||
| 
 | ||||
|     open_file("test.frm"); | ||||
|     open_file("main.cpp"); | ||||
| 
 | ||||
|     update_actions(); | ||||
|     return app.exec(); | ||||
|  | @ -484,6 +484,7 @@ static void rehighlight() | |||
|         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); | ||||
|  |  | |||
|  | @ -15,8 +15,10 @@ class GTextDocumentLine; | |||
| struct GTextDocumentSpan { | ||||
|     GTextRange range; | ||||
|     Color color; | ||||
|     Optional<Color> background_color; | ||||
|     bool is_skippable { false }; | ||||
|     const Font* font { nullptr }; | ||||
|     void* data { nullptr }; | ||||
| }; | ||||
| 
 | ||||
| class GTextDocument : public RefCounted<GTextDocument> { | ||||
|  | @ -54,6 +56,7 @@ public: | |||
| 
 | ||||
|     bool has_spans() const { return !m_spans.is_empty(); } | ||||
|     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 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) { | ||||
|                     const Font* font = &this->font(); | ||||
|                     Color color; | ||||
|                     Optional<Color> background_color; | ||||
|                     GTextPosition physical_position(line_index, start_of_visual_line + i); | ||||
|                     // FIXME: This is *horribly* inefficient.
 | ||||
|                     for (auto& span : document().spans()) { | ||||
|  | @ -397,8 +398,11 @@ void GTextEditor::paint_event(GPaintEvent& event) | |||
|                         color = span.color; | ||||
|                         if (span.font) | ||||
|                             font = span.font; | ||||
|                         background_color = span.background_color; | ||||
|                         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); | ||||
|                     character_rect.move_by(advance, 0); | ||||
|                 } | ||||
|  | @ -1135,6 +1139,7 @@ void GTextEditor::set_cursor(const GTextPosition& a_position) | |||
|         update(old_cursor_line_rect); | ||||
|         update_cursor(); | ||||
|     } | ||||
|     cursor_did_change(); | ||||
|     if (on_cursor_change) | ||||
|         on_cursor_change(); | ||||
| } | ||||
|  |  | |||
|  | @ -120,6 +120,7 @@ protected: | |||
|     virtual void leave_event(CEvent&) override; | ||||
|     virtual void context_menu_event(GContextMenuEvent&) override; | ||||
|     virtual void resize_event(GResizeEvent&) override; | ||||
|     virtual void cursor_did_change() {} | ||||
| 
 | ||||
|     GTextPosition text_position_at(const Point&) const; | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling