mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 15:42:44 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			195 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			195 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2020-2022, the SerenityOS developers.
 | |
|  *
 | |
|  * SPDX-License-Identifier: BSD-2-Clause
 | |
|  */
 | |
| 
 | |
| #include <LibGUI/TextEditor.h>
 | |
| #include <LibGfx/Color.h>
 | |
| #include <LibSyntax/Highlighter.h>
 | |
| 
 | |
| namespace Syntax {
 | |
| 
 | |
| void Highlighter::highlight_matching_token_pair()
 | |
| {
 | |
|     auto& document = m_client->get_document();
 | |
| 
 | |
|     enum class Direction {
 | |
|         Forward,
 | |
|         Backward,
 | |
|     };
 | |
| 
 | |
|     auto find_span_of_type = [&](auto i, u64 type, u64 not_type, Direction direction) -> Optional<size_t> {
 | |
|         size_t nesting_level = 0;
 | |
|         bool forward = direction == Direction::Forward;
 | |
| 
 | |
|         if (forward) {
 | |
|             ++i;
 | |
|             if (i >= document.spans().size())
 | |
|                 return {};
 | |
|         } else {
 | |
|             if (i == 0)
 | |
|                 return {};
 | |
|             --i;
 | |
|         }
 | |
| 
 | |
|         for (;;) {
 | |
|             auto& span = document.spans().at(i);
 | |
|             auto span_token_type = span.data;
 | |
|             if (token_types_equal(span_token_type, not_type)) {
 | |
|                 ++nesting_level;
 | |
|             } else if (token_types_equal(span_token_type, type)) {
 | |
|                 if (nesting_level-- <= 0)
 | |
|                     return i;
 | |
|             }
 | |
| 
 | |
|             if (forward) {
 | |
|                 ++i;
 | |
|                 if (i >= document.spans().size())
 | |
|                     return {};
 | |
|             } else {
 | |
|                 if (i == 0)
 | |
|                     return {};
 | |
|                 --i;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return {};
 | |
|     };
 | |
| 
 | |
|     auto make_buddies = [&](int index0, int index1) {
 | |
|         auto& buddy0 = document.spans()[index0];
 | |
|         auto& buddy1 = 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.attributes.background_color = Color::DarkCyan;
 | |
|         buddy1.attributes.background_color = Color::DarkCyan;
 | |
|         buddy0.attributes.color = Color::White;
 | |
|         buddy1.attributes.color = Color::White;
 | |
|         m_client->do_update();
 | |
|     };
 | |
| 
 | |
|     auto pairs = matching_token_pairs();
 | |
| 
 | |
|     for (size_t i = 0; i < document.spans().size(); ++i) {
 | |
|         auto& span = const_cast<GUI::TextDocumentSpan&>(document.spans().at(i));
 | |
|         auto token_type = span.data;
 | |
| 
 | |
|         for (auto& pair : pairs) {
 | |
|             if (token_types_equal(token_type, pair.open) && span.range.start() == m_client->get_cursor()) {
 | |
|                 auto buddy = find_span_of_type(i, pair.close, pair.open, Direction::Forward);
 | |
|                 if (buddy.has_value())
 | |
|                     make_buddies(i, buddy.value());
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         for (auto& pair : pairs) {
 | |
|             if (token_types_equal(token_type, pair.close) && span.range.end() == m_client->get_cursor()) {
 | |
|                 auto buddy = find_span_of_type(i, pair.open, pair.close, Direction::Backward);
 | |
|                 if (buddy.has_value())
 | |
|                     make_buddies(i, buddy.value());
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Highlighter::attach(HighlighterClient& client)
 | |
| {
 | |
|     VERIFY(!m_client);
 | |
|     m_client = &client;
 | |
| }
 | |
| 
 | |
| void Highlighter::detach()
 | |
| {
 | |
|     m_client = nullptr;
 | |
| }
 | |
| 
 | |
| void Highlighter::cursor_did_change()
 | |
| {
 | |
|     auto& document = m_client->get_document();
 | |
|     if (m_has_brace_buddies) {
 | |
|         if (m_brace_buddies[0].index >= 0 && m_brace_buddies[0].index < static_cast<int>(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 < static_cast<int>(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_client->do_update();
 | |
|     }
 | |
|     highlight_matching_token_pair();
 | |
| }
 | |
| 
 | |
| Vector<Highlighter::MatchingTokenPair> Highlighter::matching_token_pairs() const
 | |
| {
 | |
|     auto own_pairs = matching_token_pairs_impl();
 | |
|     own_pairs.ensure_capacity(own_pairs.size() + m_nested_token_pairs.size());
 | |
|     for (auto& nested_pair : m_nested_token_pairs)
 | |
|         own_pairs.append(nested_pair);
 | |
|     return own_pairs;
 | |
| }
 | |
| 
 | |
| void Highlighter::register_nested_token_pairs(Vector<MatchingTokenPair> pairs)
 | |
| {
 | |
|     for (auto& pair : pairs)
 | |
|         m_nested_token_pairs.set(pair);
 | |
| }
 | |
| 
 | |
| StringView language_to_string(Language language)
 | |
| {
 | |
|     switch (language) {
 | |
|     case Language::Cpp:
 | |
|         return "C++"sv;
 | |
|     case Language::CSS:
 | |
|         return "CSS"sv;
 | |
|     case Language::GitCommit:
 | |
|         return "Git"sv;
 | |
|     case Language::GML:
 | |
|         return "GML"sv;
 | |
|     case Language::HTML:
 | |
|         return "HTML"sv;
 | |
|     case Language::INI:
 | |
|         return "INI"sv;
 | |
|     case Language::JavaScript:
 | |
|         return "JavaScript"sv;
 | |
|     case Language::PlainText:
 | |
|         return "Plain Text"sv;
 | |
|     case Language::Shell:
 | |
|         return "Shell"sv;
 | |
|     case Language::SQL:
 | |
|         return "SQL"sv;
 | |
|     }
 | |
|     VERIFY_NOT_REACHED();
 | |
| }
 | |
| 
 | |
| StringView common_language_extension(Language language)
 | |
| {
 | |
|     switch (language) {
 | |
|     case Language::Cpp:
 | |
|         return "cpp"sv;
 | |
|     case Language::CSS:
 | |
|         return "css"sv;
 | |
|     case Language::GitCommit:
 | |
|         return {};
 | |
|     case Language::GML:
 | |
|         return "gml"sv;
 | |
|     case Language::HTML:
 | |
|         return "html"sv;
 | |
|     case Language::INI:
 | |
|         return "ini"sv;
 | |
|     case Language::JavaScript:
 | |
|         return "js"sv;
 | |
|     case Language::PlainText:
 | |
|         return "txt"sv;
 | |
|     case Language::Shell:
 | |
|         return "sh"sv;
 | |
|     case Language::SQL:
 | |
|         return "sql"sv;
 | |
|     }
 | |
|     VERIFY_NOT_REACHED();
 | |
| }
 | |
| 
 | |
| }
 | 
