summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Atkins <atkinssj@serenityos.org>2023-03-04 17:16:24 +0000
committerAndreas Kling <kling@serenityos.org>2023-03-05 16:54:10 +0100
commit822164a6864d4e43b09607bc787dac1622ebe6fd (patch)
treee3c267c3caf99c8b6af3170d63429bade0092f0d
parent631927470a2a90cc332720eccb47867e6037f88a (diff)
downloadserenity-822164a6864d4e43b09607bc787dac1622ebe6fd.zip
LibCMake: Add folding regions to syntax highlighter
This creates folding regions for blocks defined by the following: - if/elseif/else/endif - foreach/endforeach - while/endwhile - macro/endmacro - function/endfunction Since there is no guarantee that each keyword will have a matching partner, we do our best by looking for the most recent possible start token matching the current end token. If we find one, we link to it and drop all the other start-tokens that happened in between. For example, we would define a folding region for this invalid file like so: [-] if(TRUE) │ while() └─ endif()
-rw-r--r--Userland/Libraries/LibCMake/SyntaxHighlighter.cpp106
1 files changed, 106 insertions, 0 deletions
diff --git a/Userland/Libraries/LibCMake/SyntaxHighlighter.cpp b/Userland/Libraries/LibCMake/SyntaxHighlighter.cpp
index d5f85cbb41..60856847db 100644
--- a/Userland/Libraries/LibCMake/SyntaxHighlighter.cpp
+++ b/Userland/Libraries/LibCMake/SyntaxHighlighter.cpp
@@ -49,7 +49,15 @@ void SyntaxHighlighter::rehighlight(Gfx::Palette const& palette)
{
auto text = m_client->get_text();
auto tokens = Lexer::lex(text).release_value_but_fixme_should_propagate_errors();
+ auto& document = m_client->get_document();
+ struct OpenBlock {
+ Token token;
+ int open_paren_count { 0 };
+ Optional<Token> ending_paren {};
+ };
+ Vector<OpenBlock> open_blocks;
+ Vector<GUI::TextDocumentFoldingRegion> folding_regions;
Vector<GUI::TextDocumentSpan> spans;
auto highlight_span = [&](Token::Type type, Position const& start, Position const& end) {
GUI::TextDocumentSpan span;
@@ -71,6 +79,43 @@ void SyntaxHighlighter::rehighlight(Gfx::Palette const& palette)
spans.append(move(span));
};
+ auto create_region_from_block_type = [&](auto control_keywords, Token const& end_token) {
+ if (open_blocks.is_empty())
+ return;
+
+ // Find the most recent open block with a matching keyword.
+ Optional<size_t> found_index;
+ OpenBlock open_block;
+ for (int i = open_blocks.size() - 1; i >= 0; i--) {
+ for (auto value : control_keywords) {
+ if (open_blocks[i].token.control_keyword == value) {
+ found_index = i;
+ open_block = open_blocks[i];
+ break;
+ }
+ }
+ if (found_index.has_value())
+ break;
+ }
+
+ if (found_index.has_value()) {
+ // Remove the found token and all after it.
+ open_blocks.shrink(found_index.value());
+
+ // Create a region.
+ GUI::TextDocumentFoldingRegion region;
+ if (open_block.ending_paren.has_value()) {
+ region.range.set_start({ open_block.ending_paren->end.line, open_block.ending_paren->end.column });
+ } else {
+ // The opening command is invalid, it does not have a closing paren.
+ // So, we just start the region at the end of the line where the command identifier was. (eg, `if`)
+ region.range.set_start({ open_block.token.end.line, document.line(open_block.token.end.line).last_non_whitespace_column().value() });
+ }
+ region.range.set_end({ end_token.start.line, end_token.start.column });
+ folding_regions.append(move(region));
+ }
+ };
+
for (auto const& token : tokens) {
if (token.type == Token::Type::QuotedArgument || token.type == Token::Type::UnquotedArgument) {
// Alternately highlight the regular/variable-reference parts.
@@ -86,8 +131,69 @@ void SyntaxHighlighter::rehighlight(Gfx::Palette const& palette)
}
highlight_span(token.type, token.start, token.end);
+
+ if (!open_blocks.is_empty() && !open_blocks.last().ending_paren.has_value()) {
+ auto& open_block = open_blocks.last();
+ if (token.type == Token::Type::OpenParen) {
+ open_block.open_paren_count++;
+ } else if (token.type == Token::Type::CloseParen) {
+ open_block.open_paren_count--;
+ if (open_block.open_paren_count == 0)
+ open_block.ending_paren = token;
+ }
+ }
+
+ // Create folding regions from control-keyword blocks.
+ if (token.type == Token::Type::ControlKeyword) {
+ switch (token.control_keyword.value()) {
+ case ControlKeywordType::If:
+ open_blocks.empend(token);
+ break;
+ case ControlKeywordType::ElseIf:
+ case ControlKeywordType::Else:
+ create_region_from_block_type(Array { ControlKeywordType::If, ControlKeywordType::ElseIf }, token);
+ open_blocks.empend(token);
+ break;
+ case ControlKeywordType::EndIf:
+ create_region_from_block_type(Array { ControlKeywordType::If, ControlKeywordType::ElseIf, ControlKeywordType::Else }, token);
+ break;
+ case ControlKeywordType::ForEach:
+ open_blocks.empend(token);
+ break;
+ case ControlKeywordType::EndForEach:
+ create_region_from_block_type(Array { ControlKeywordType::ForEach }, token);
+ break;
+ case ControlKeywordType::While:
+ open_blocks.empend(token);
+ break;
+ case ControlKeywordType::EndWhile:
+ create_region_from_block_type(Array { ControlKeywordType::While }, token);
+ break;
+ case ControlKeywordType::Macro:
+ open_blocks.empend(token);
+ break;
+ case ControlKeywordType::EndMacro:
+ create_region_from_block_type(Array { ControlKeywordType::Macro }, token);
+ break;
+ case ControlKeywordType::Function:
+ open_blocks.empend(token);
+ break;
+ case ControlKeywordType::EndFunction:
+ create_region_from_block_type(Array { ControlKeywordType::Function }, token);
+ break;
+ case ControlKeywordType::Block:
+ open_blocks.empend(token);
+ break;
+ case ControlKeywordType::EndBlock:
+ create_region_from_block_type(Array { ControlKeywordType::Block }, token);
+ break;
+ default:
+ break;
+ }
+ }
}
m_client->do_set_spans(move(spans));
+ m_client->do_set_folding_regions(move(folding_regions));
m_has_brace_buddies = false;
highlight_matching_token_pair();