From ba4db899d4f6b265bfa17bf17809da1660ffdd6f Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Tue, 29 Aug 2023 12:43:41 +0330 Subject: [PATCH] LibSyntax+Userland: Make LibSyntax not depend on LibGUI This moves some stuff around to make LibGUI depend on LibSyntax instead of the other way around, as not every application that wishes to do syntax highlighting is necessarily a LibGUI (or even a GUI) application. --- .../LibCMake/CMakeCache/SyntaxHighlighter.cpp | 4 +- .../Libraries/LibCMake/SyntaxHighlighter.cpp | 8 +- Userland/Libraries/LibGUI/CMakeLists.txt | 3 +- Userland/Libraries/LibGUI/Forward.h | 5 +- .../LibGUI/GML/SyntaxHighlighter.cpp | 8 +- .../LibGUI/GitCommitSyntaxHighlighter.cpp | 4 +- .../Libraries/LibGUI/INISyntaxHighlighter.cpp | 10 +- Userland/Libraries/LibGUI/TextDocument.cpp | 371 ----------------- Userland/Libraries/LibGUI/TextDocument.h | 86 +--- Userland/Libraries/LibGUI/TextEditor.h | 6 +- Userland/Libraries/LibGUI/TextPosition.h | 40 +- Userland/Libraries/LibGUI/TextRange.h | 71 +--- .../Libraries/LibJS/SyntaxHighlighter.cpp | 14 +- .../LibMarkdown/SyntaxHighlighter.cpp | 10 +- .../LibSQL/AST/SyntaxHighlighter.cpp | 4 +- Userland/Libraries/LibSyntax/CMakeLists.txt | 1 + Userland/Libraries/LibSyntax/Document.cpp | 389 ++++++++++++++++++ Userland/Libraries/LibSyntax/Document.h | 101 +++++ Userland/Libraries/LibSyntax/Forward.h | 3 + Userland/Libraries/LibSyntax/Highlighter.h | 38 +- .../Libraries/LibSyntax/HighlighterClient.h | 29 +- Userland/Libraries/LibSyntax/TextPosition.h | 53 +++ Userland/Libraries/LibSyntax/TextRange.h | 80 ++++ .../SyntaxHighlighter/SyntaxHighlighter.cpp | 8 +- .../SyntaxHighlighter/SyntaxHighlighter.cpp | 10 +- Userland/Shell/SyntaxHighlighter.cpp | 100 ++--- 26 files changed, 773 insertions(+), 683 deletions(-) create mode 100644 Userland/Libraries/LibSyntax/Document.cpp create mode 100644 Userland/Libraries/LibSyntax/Document.h create mode 100644 Userland/Libraries/LibSyntax/TextPosition.h create mode 100644 Userland/Libraries/LibSyntax/TextRange.h diff --git a/Userland/Libraries/LibCMake/CMakeCache/SyntaxHighlighter.cpp b/Userland/Libraries/LibCMake/CMakeCache/SyntaxHighlighter.cpp index ad83a04091..b04df0be4b 100644 --- a/Userland/Libraries/LibCMake/CMakeCache/SyntaxHighlighter.cpp +++ b/Userland/Libraries/LibCMake/CMakeCache/SyntaxHighlighter.cpp @@ -42,9 +42,9 @@ 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(); - Vector spans; + Vector spans; auto highlight_span = [&](Token::Type type, Position const& start, Position const& end) { - GUI::TextDocumentSpan span; + Syntax::TextDocumentSpan span; span.range.set_start({ start.line, start.column }); span.range.set_end({ end.line, end.column }); if (!span.range.is_valid()) diff --git a/Userland/Libraries/LibCMake/SyntaxHighlighter.cpp b/Userland/Libraries/LibCMake/SyntaxHighlighter.cpp index 6873657f28..11071d4b6f 100644 --- a/Userland/Libraries/LibCMake/SyntaxHighlighter.cpp +++ b/Userland/Libraries/LibCMake/SyntaxHighlighter.cpp @@ -57,10 +57,10 @@ void SyntaxHighlighter::rehighlight(Gfx::Palette const& palette) Optional ending_paren {}; }; Vector open_blocks; - Vector folding_regions; - Vector spans; + Vector folding_regions; + Vector spans; auto highlight_span = [&](Token::Type type, Position const& start, Position const& end) { - GUI::TextDocumentSpan span; + Syntax::TextDocumentSpan span; span.range.set_start({ start.line, start.column }); span.range.set_end({ end.line, end.column }); if (!span.range.is_valid()) @@ -96,7 +96,7 @@ void SyntaxHighlighter::rehighlight(Gfx::Palette const& palette) open_blocks.shrink(found_index.value()); // Create a region. - GUI::TextDocumentFoldingRegion region; + Syntax::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 { diff --git a/Userland/Libraries/LibGUI/CMakeLists.txt b/Userland/Libraries/LibGUI/CMakeLists.txt index 872bfe9402..4c60bdb3c2 100644 --- a/Userland/Libraries/LibGUI/CMakeLists.txt +++ b/Userland/Libraries/LibGUI/CMakeLists.txt @@ -146,4 +146,5 @@ set(GENERATED_SOURCES ) serenity_lib(LibGUI gui) -target_link_libraries(LibGUI PRIVATE LibCore LibFileSystem LibGfx LibIPC LibThreading LibRegex LibSyntax LibConfig LibUnicode) +target_link_libraries(LibGUI PRIVATE LibCore LibFileSystem LibGfx LibIPC LibThreading LibRegex LibConfig LibUnicode) +target_link_libraries(LibGUI PUBLIC LibSyntax) diff --git a/Userland/Libraries/LibGUI/Forward.h b/Userland/Libraries/LibGUI/Forward.h index 3ee49c9f26..da238609f7 100644 --- a/Userland/Libraries/LibGUI/Forward.h +++ b/Userland/Libraries/LibGUI/Forward.h @@ -6,6 +6,8 @@ #pragma once +#include + namespace GUI { class AbstractButton; @@ -77,11 +79,8 @@ class Statusbar; class TabWidget; class TableView; class TextBox; -class TextPosition; class UrlBox; class TextDocument; -class TextDocumentLine; -struct TextDocumentSpan; class TextDocumentUndoCommand; class TextEditor; class ThemeChangeEvent; diff --git a/Userland/Libraries/LibGUI/GML/SyntaxHighlighter.cpp b/Userland/Libraries/LibGUI/GML/SyntaxHighlighter.cpp index 6cc8a79c87..19ac8f0075 100644 --- a/Userland/Libraries/LibGUI/GML/SyntaxHighlighter.cpp +++ b/Userland/Libraries/LibGUI/GML/SyntaxHighlighter.cpp @@ -47,11 +47,11 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) Vector folding_region_start_tokens; - Vector spans; - Vector folding_regions; + Vector spans; + Vector folding_regions; for (auto& token : tokens) { - GUI::TextDocumentSpan span; + Syntax::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 }); span.attributes = style_for_token_type(palette, token.m_type); @@ -65,7 +65,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) } else if (token.m_type == Token::Type::RightCurly) { if (!folding_region_start_tokens.is_empty()) { auto left_curly = folding_region_start_tokens.take_last(); - GUI::TextDocumentFoldingRegion region; + Syntax::TextDocumentFoldingRegion region; region.range.set_start({ left_curly.m_end.line, left_curly.m_end.column }); region.range.set_end({ token.m_start.line, token.m_start.column }); folding_regions.append(region); diff --git a/Userland/Libraries/LibGUI/GitCommitSyntaxHighlighter.cpp b/Userland/Libraries/LibGUI/GitCommitSyntaxHighlighter.cpp index 15ef75b051..e113657b0b 100644 --- a/Userland/Libraries/LibGUI/GitCommitSyntaxHighlighter.cpp +++ b/Userland/Libraries/LibGUI/GitCommitSyntaxHighlighter.cpp @@ -26,9 +26,9 @@ void GitCommitSyntaxHighlighter::rehighlight(Palette const& palette) GitCommitLexer lexer(text); auto tokens = lexer.lex(); - Vector spans; + Vector spans; for (auto& token : tokens) { - GUI::TextDocumentSpan span; + Syntax::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 }); span.attributes = style_for_token_type(palette, token.m_type); diff --git a/Userland/Libraries/LibGUI/INISyntaxHighlighter.cpp b/Userland/Libraries/LibGUI/INISyntaxHighlighter.cpp index d46d83c7d1..2db189ed71 100644 --- a/Userland/Libraries/LibGUI/INISyntaxHighlighter.cpp +++ b/Userland/Libraries/LibGUI/INISyntaxHighlighter.cpp @@ -45,11 +45,11 @@ void IniSyntaxHighlighter::rehighlight(Palette const& palette) Optional previous_section_token; IniToken previous_token; - Vector folding_regions; + Vector folding_regions; - Vector spans; + Vector spans; for (auto& token : tokens) { - GUI::TextDocumentSpan span; + Syntax::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 }); span.attributes = style_for_token_type(palette, token.m_type); @@ -61,7 +61,7 @@ void IniSyntaxHighlighter::rehighlight(Palette const& palette) previous_section_token = token; } else if (token.m_type == IniToken::Type::LeftBracket) { if (previous_section_token.has_value()) { - TextDocumentFoldingRegion region; + Syntax::TextDocumentFoldingRegion region; region.range.set_start({ previous_section_token->m_end.line, previous_section_token->m_end.column }); // If possible, leave a blank line between sections. // `end_line - start_line > 1` means the whitespace contains at least 1 blank line, @@ -80,7 +80,7 @@ void IniSyntaxHighlighter::rehighlight(Palette const& palette) previous_token = token; } if (previous_section_token.has_value()) { - TextDocumentFoldingRegion region; + Syntax::TextDocumentFoldingRegion region; auto& end_token = tokens.last(); region.range.set_start({ previous_section_token->m_end.line, previous_section_token->m_end.column }); region.range.set_end({ end_token.m_end.line, end_token.m_end.column }); diff --git a/Userland/Libraries/LibGUI/TextDocument.cpp b/Userland/Libraries/LibGUI/TextDocument.cpp index 2fbd5b0424..639c2b7772 100644 --- a/Userland/Libraries/LibGUI/TextDocument.cpp +++ b/Userland/Libraries/LibGUI/TextDocument.cpp @@ -105,173 +105,6 @@ bool TextDocument::set_text(StringView text, AllowCallback allow_callback, IsNew return true; } -size_t TextDocumentLine::first_non_whitespace_column() const -{ - for (size_t i = 0; i < length(); ++i) { - auto code_point = code_points()[i]; - if (!is_ascii_space(code_point)) - return i; - } - return length(); -} - -Optional TextDocumentLine::last_non_whitespace_column() const -{ - for (ssize_t i = length() - 1; i >= 0; --i) { - auto code_point = code_points()[i]; - if (!is_ascii_space(code_point)) - return i; - } - return {}; -} - -bool TextDocumentLine::ends_in_whitespace() const -{ - if (!length()) - return false; - return is_ascii_space(code_points()[length() - 1]); -} - -bool TextDocumentLine::can_select() const -{ - if (is_empty()) - return false; - for (size_t i = 0; i < length(); ++i) { - auto code_point = code_points()[i]; - if (code_point != '\n' && code_point != '\r' && code_point != '\f' && code_point != '\v') - return true; - } - return false; -} - -size_t TextDocumentLine::leading_spaces() const -{ - size_t count = 0; - for (; count < m_text.size(); ++count) { - if (m_text[count] != ' ') { - break; - } - } - return count; -} - -DeprecatedString TextDocumentLine::to_utf8() const -{ - StringBuilder builder; - builder.append(view()); - return builder.to_deprecated_string(); -} - -TextDocumentLine::TextDocumentLine(TextDocument& document) -{ - clear(document); -} - -TextDocumentLine::TextDocumentLine(TextDocument& document, StringView text) -{ - set_text(document, text); -} - -void TextDocumentLine::clear(TextDocument& document) -{ - m_text.clear(); - document.update_views({}); -} - -void TextDocumentLine::set_text(TextDocument& document, Vector const text) -{ - m_text = move(text); - document.update_views({}); -} - -bool TextDocumentLine::set_text(TextDocument& document, StringView text) -{ - if (text.is_empty()) { - clear(document); - return true; - } - m_text.clear(); - Utf8View utf8_view(text); - if (!utf8_view.validate()) { - return false; - } - for (auto code_point : utf8_view) - m_text.append(code_point); - document.update_views({}); - return true; -} - -void TextDocumentLine::append(TextDocument& document, u32 const* code_points, size_t length) -{ - if (length == 0) - return; - m_text.append(code_points, length); - document.update_views({}); -} - -void TextDocumentLine::append(TextDocument& document, u32 code_point) -{ - insert(document, length(), code_point); -} - -void TextDocumentLine::prepend(TextDocument& document, u32 code_point) -{ - insert(document, 0, code_point); -} - -void TextDocumentLine::insert(TextDocument& document, size_t index, u32 code_point) -{ - if (index == length()) { - m_text.append(code_point); - } else { - m_text.insert(index, code_point); - } - document.update_views({}); -} - -void TextDocumentLine::remove(TextDocument& document, size_t index) -{ - if (index == length()) { - m_text.take_last(); - } else { - m_text.remove(index); - } - document.update_views({}); -} - -void TextDocumentLine::remove_range(TextDocument& document, size_t start, size_t length) -{ - VERIFY(length <= m_text.size()); - - Vector new_data; - new_data.ensure_capacity(m_text.size() - length); - for (size_t i = 0; i < start; ++i) - new_data.append(m_text[i]); - for (size_t i = (start + length); i < m_text.size(); ++i) - new_data.append(m_text[i]); - m_text = move(new_data); - document.update_views({}); -} - -void TextDocumentLine::keep_range(TextDocument& document, size_t start_index, size_t length) -{ - VERIFY(start_index + length < m_text.size()); - - Vector new_data; - new_data.ensure_capacity(m_text.size()); - for (size_t i = start_index; i <= (start_index + length); i++) - new_data.append(m_text[i]); - - m_text = move(new_data); - document.update_views({}); -} - -void TextDocumentLine::truncate(TextDocument& document, size_t length) -{ - m_text.resize(length); - document.update_views({}); -} - void TextDocument::append_line(NonnullOwnPtr line) { lines().append(move(line)); @@ -1303,213 +1136,9 @@ TextRange TextDocument::range_for_entire_line(size_t line_index) const return { { line_index, 0 }, { line_index, line(line_index).length() } }; } -TextDocumentSpan const* TextDocument::span_at(TextPosition const& position) const -{ - for (auto& span : m_spans) { - if (span.range.contains(position)) - return &span; - } - return nullptr; -} - void TextDocument::set_unmodified() { m_undo_stack.set_current_unmodified(); } -void TextDocument::set_spans(u32 span_collection_index, Vector spans) -{ - m_span_collections.set(span_collection_index, move(spans)); - merge_span_collections(); -} - -struct SpanAndCollectionIndex { - TextDocumentSpan span; - u32 collection_index { 0 }; -}; - -void TextDocument::merge_span_collections() -{ - Vector sorted_spans; - auto collection_indices = m_span_collections.keys(); - quick_sort(collection_indices); - - for (auto collection_index : collection_indices) { - auto spans = m_span_collections.get(collection_index).value(); - for (auto span : spans) { - sorted_spans.append({ move(span), collection_index }); - } - } - - quick_sort(sorted_spans, [](SpanAndCollectionIndex const& a, SpanAndCollectionIndex const& b) { - if (a.span.range.start() == b.span.range.start()) { - return a.collection_index < b.collection_index; - } - return a.span.range.start() < b.span.range.start(); - }); - - // The end of the TextRanges of spans are non-inclusive, i.e span range = [X,y). - // This transforms the span's range to be inclusive, i.e [X,Y]. - auto adjust_end = [](GUI::TextDocumentSpan span) -> GUI::TextDocumentSpan { - span.range.set_end({ span.range.end().line(), span.range.end().column() == 0 ? 0 : span.range.end().column() - 1 }); - return span; - }; - - Vector merged_spans; - for (auto& span_and_collection_index : sorted_spans) { - if (merged_spans.is_empty()) { - merged_spans.append(span_and_collection_index); - continue; - } - - auto const& span = span_and_collection_index.span; - auto last_span_and_collection_index = merged_spans.last(); - auto const& last_span = last_span_and_collection_index.span; - - if (adjust_end(span).range.start() > adjust_end(last_span).range.end()) { - // Current span does not intersect with previous one, can simply append to merged list. - merged_spans.append(span_and_collection_index); - continue; - } - merged_spans.take_last(); - - if (span.range.start() > last_span.range.start()) { - SpanAndCollectionIndex first_part = last_span_and_collection_index; - first_part.span.range.set_end(span.range.start()); - merged_spans.append(move(first_part)); - } - - SpanAndCollectionIndex merged_span; - merged_span.collection_index = span_and_collection_index.collection_index; - merged_span.span.range = { span.range.start(), min(span.range.end(), last_span.range.end()) }; - merged_span.span.is_skippable = span.is_skippable | last_span.is_skippable; - merged_span.span.data = span.data ? span.data : last_span.data; - merged_span.span.attributes.color = span_and_collection_index.collection_index > last_span_and_collection_index.collection_index ? span.attributes.color : last_span.attributes.color; - merged_span.span.attributes.bold = span.attributes.bold | last_span.attributes.bold; - merged_span.span.attributes.background_color = span.attributes.background_color.has_value() ? span.attributes.background_color.value() : last_span.attributes.background_color; - merged_span.span.attributes.underline_color = span.attributes.underline_color.has_value() ? span.attributes.underline_color.value() : last_span.attributes.underline_color; - merged_span.span.attributes.underline_style = span.attributes.underline_style.has_value() ? span.attributes.underline_style : last_span.attributes.underline_style; - merged_spans.append(move(merged_span)); - - if (span.range.end() == last_span.range.end()) - continue; - - if (span.range.end() > last_span.range.end()) { - SpanAndCollectionIndex last_part = span_and_collection_index; - last_part.span.range.set_start(last_span.range.end()); - merged_spans.append(move(last_part)); - continue; - } - - SpanAndCollectionIndex last_part = last_span_and_collection_index; - last_part.span.range.set_start(span.range.end()); - merged_spans.append(move(last_part)); - } - - m_spans.clear(); - TextDocumentSpan previous_span { .range = { TextPosition(0, 0), TextPosition(0, 0) }, .attributes = {} }; - for (auto span : merged_spans) { - // Validate spans - if (!span.span.range.is_valid()) { - dbgln_if(TEXTEDITOR_DEBUG, "Invalid span {} => ignoring", span.span.range); - continue; - } - if (span.span.range.end() < span.span.range.start()) { - dbgln_if(TEXTEDITOR_DEBUG, "Span {} has negative length => ignoring", span.span.range); - continue; - } - if (span.span.range.end() < previous_span.range.start()) { - dbgln_if(TEXTEDITOR_DEBUG, "Spans not sorted (Span {} ends before previous span {}) => ignoring", span.span.range, previous_span.range); - continue; - } - if (span.span.range.start() < previous_span.range.end()) { - dbgln_if(TEXTEDITOR_DEBUG, "Span {} overlaps previous span {} => ignoring", span.span.range, previous_span.range); - continue; - } - - previous_span = span.span; - m_spans.append(move(span.span)); - } -} - -void TextDocument::set_folding_regions(Vector folding_regions) -{ - // Remove any regions that don't span at least 3 lines. - // Currently, we can't do anything useful with them, and our implementation gets very confused by - // single-line regions, so drop them. - folding_regions.remove_all_matching([](TextDocumentFoldingRegion const& region) { - return region.range.line_count() < 3; - }); - - quick_sort(folding_regions, [](TextDocumentFoldingRegion const& a, TextDocumentFoldingRegion const& b) { - return a.range.start() < b.range.start(); - }); - - for (auto& folding_region : folding_regions) { - folding_region.line_ptr = &line(folding_region.range.start().line()); - - // Map the new folding region to an old one, to preserve which regions were folded. - // FIXME: This is O(n*n). - for (auto const& existing_folding_region : m_folding_regions) { - // We treat two folding regions as the same if they start on the same TextDocumentLine, - // and have the same line count. The actual line *numbers* might change, but the pointer - // and count should not. - if (existing_folding_region.line_ptr - && existing_folding_region.line_ptr == folding_region.line_ptr - && existing_folding_region.range.line_count() == folding_region.range.line_count()) { - folding_region.is_folded = existing_folding_region.is_folded; - break; - } - } - } - - // FIXME: Remove any regions that partially overlap another region, since these are invalid. - - m_folding_regions = move(folding_regions); - - if constexpr (TEXTEDITOR_DEBUG) { - dbgln("TextDocument got {} fold regions:", m_folding_regions.size()); - for (auto const& item : m_folding_regions) { - dbgln("- {} (ptr: {:p}, folded: {})", item.range, item.line_ptr, item.is_folded); - } - } -} - -Optional TextDocument::folding_region_starting_on_line(size_t line) -{ - return m_folding_regions.first_matching([line](auto& region) { - return region.range.start().line() == line; - }); -} - -bool TextDocument::line_is_visible(size_t line) const -{ - // FIXME: line_is_visible() gets called a lot. - // We could avoid a lot of repeated work if we saved this state on the TextDocumentLine. - return !any_of(m_folding_regions, [line](auto& region) { - return region.is_folded - && line > region.range.start().line() - && line < region.range.end().line(); - }); -} - -Vector TextDocument::currently_folded_regions() const -{ - Vector folded_regions; - - for (auto& region : m_folding_regions) { - if (region.is_folded) { - // Only add this region if it's not contained within a previous folded region. - // Because regions are sorted by their start position, and regions cannot partially overlap, - // we can just see if it starts inside the last region we appended. - if (!folded_regions.is_empty() && folded_regions.last().range.contains(region.range.start())) - continue; - - folded_regions.append(region); - } - } - - return folded_regions; -} - } diff --git a/Userland/Libraries/LibGUI/TextDocument.h b/Userland/Libraries/LibGUI/TextDocument.h index 74db33db0b..1ea3be4417 100644 --- a/Userland/Libraries/LibGUI/TextDocument.h +++ b/Userland/Libraries/LibGUI/TextDocument.h @@ -17,32 +17,24 @@ #include #include #include +#include #include #include #include #include #include #include +#include namespace GUI { +using TextDocumentSpan = Syntax::TextDocumentSpan; +using TextDocumentFoldingRegion = Syntax::TextDocumentFoldingRegion; +using TextDocumentLine = Syntax::TextDocumentLine; + constexpr Duration COMMAND_COMMIT_TIME = Duration::from_milliseconds(400); -struct TextDocumentSpan { - TextRange range; - Gfx::TextAttributes attributes; - u64 data { 0 }; - bool is_skippable { false }; -}; - -struct TextDocumentFoldingRegion { - TextRange range; - bool is_folded { false }; - // This pointer is only used to identify that two TDFRs are the same. - RawPtr line_ptr; -}; - -class TextDocument : public RefCounted { +class TextDocument : public Syntax::Document { public: enum class SearchShouldWrap { No = 0, @@ -69,10 +61,8 @@ public: virtual ~TextDocument() = default; size_t line_count() const { return m_lines.size(); } - TextDocumentLine const& line(size_t line_index) const { return *m_lines[line_index]; } - TextDocumentLine& line(size_t line_index) { return *m_lines[line_index]; } - - void set_spans(u32 span_collection_index, Vector spans); + virtual TextDocumentLine const& line(size_t line_index) const override { return *m_lines[line_index]; } + virtual TextDocumentLine& line(size_t line_index) override { return *m_lines[line_index]; } enum class IsNewDocument { No, @@ -83,23 +73,6 @@ public: Vector> const& lines() const { return m_lines; } Vector>& lines() { return m_lines; } - bool has_spans() const { return !m_spans.is_empty(); } - Vector& spans() { return m_spans; } - Vector const& spans() const { return m_spans; } - void set_span_at_index(size_t index, TextDocumentSpan span) { m_spans[index] = move(span); } - - TextDocumentSpan const* span_at(TextPosition const&) const; - - void set_folding_regions(Vector); - bool has_folding_regions() const { return !m_folding_regions.is_empty(); } - Vector& folding_regions() { return m_folding_regions; } - Vector const& folding_regions() const { return m_folding_regions; } - Optional folding_region_starting_on_line(size_t line); - // Returns all folded FoldingRegions that are not contained inside another folded region. - Vector currently_folded_regions() const; - // Returns true if any part of the line is currently visible. (Not inside a folded FoldingRegion.) - bool line_is_visible(size_t line) const; - void append_line(NonnullOwnPtr); NonnullOwnPtr take_line(size_t line_index); void remove_line(size_t line_index); @@ -109,7 +82,7 @@ public: void register_client(Client&); void unregister_client(Client&); - void update_views(Badge); + virtual void update_views(Badge) override; DeprecatedString text() const; DeprecatedString text_in_range(TextRange const&) const; @@ -162,12 +135,7 @@ protected: explicit TextDocument(Client* client); private: - void merge_span_collections(); - Vector> m_lines; - HashMap> m_span_collections; - Vector m_spans; - Vector m_folding_regions; HashTable m_clients; bool m_client_notifications_enabled { true }; @@ -182,40 +150,6 @@ private: DeprecatedString m_regex_needle; }; -class TextDocumentLine { -public: - explicit TextDocumentLine(TextDocument&); - explicit TextDocumentLine(TextDocument&, StringView); - - DeprecatedString to_utf8() const; - - Utf32View view() const { return { code_points(), length() }; } - u32 const* code_points() const { return m_text.data(); } - size_t length() const { return m_text.size(); } - bool set_text(TextDocument&, StringView); - void set_text(TextDocument&, Vector); - void append(TextDocument&, u32); - void prepend(TextDocument&, u32); - void insert(TextDocument&, size_t index, u32); - void remove(TextDocument&, size_t index); - void append(TextDocument&, u32 const*, size_t); - void truncate(TextDocument&, size_t length); - void clear(TextDocument&); - void remove_range(TextDocument&, size_t start, size_t length); - void keep_range(TextDocument&, size_t start_index, size_t end_index); - - size_t first_non_whitespace_column() const; - Optional last_non_whitespace_column() const; - bool ends_in_whitespace() const; - bool can_select() const; - bool is_empty() const { return length() == 0; } - size_t leading_spaces() const; - -private: - // NOTE: This vector is null terminated. - Vector m_text; -}; - class TextDocumentUndoCommand : public Command { public: TextDocumentUndoCommand(TextDocument&); diff --git a/Userland/Libraries/LibGUI/TextEditor.h b/Userland/Libraries/LibGUI/TextEditor.h index 8d680e92ff..789d7a54b2 100644 --- a/Userland/Libraries/LibGUI/TextEditor.h +++ b/Userland/Libraries/LibGUI/TextEditor.h @@ -17,10 +17,10 @@ #include #include #include -#include #include #include #include +#include namespace GUI { @@ -93,7 +93,7 @@ public: bool is_editable() const { return m_mode == Editable; } bool is_readonly() const { return m_mode == ReadOnly; } bool is_displayonly() const { return m_mode == DisplayOnly; } - void set_mode(const Mode); + void set_mode(Mode const); void set_editing_cursor(); @@ -294,7 +294,7 @@ protected: virtual void highlighter_did_set_folding_regions(Vector folding_regions) final; private: - friend class TextDocumentLine; + friend TextDocumentLine; // ^TextDocument::Client virtual void document_did_append_line() override; diff --git a/Userland/Libraries/LibGUI/TextPosition.h b/Userland/Libraries/LibGUI/TextPosition.h index 519302a96a..b69adde7d7 100644 --- a/Userland/Libraries/LibGUI/TextPosition.h +++ b/Userland/Libraries/LibGUI/TextPosition.h @@ -7,46 +7,10 @@ #pragma once #include +#include namespace GUI { -class TextPosition { -public: - TextPosition() = default; - TextPosition(size_t line, size_t column) - : m_line(line) - , m_column(column) - { - } - - bool is_valid() const { return m_line != 0xffffffffu && m_column != 0xffffffffu; } - - size_t line() const { return m_line; } - size_t column() const { return m_column; } - - void set_line(size_t line) { m_line = line; } - void set_column(size_t column) { m_column = column; } - - bool operator==(TextPosition const& other) const { return m_line == other.m_line && m_column == other.m_column; } - bool operator!=(TextPosition const& other) const { return m_line != other.m_line || m_column != other.m_column; } - bool operator<(TextPosition const& other) const { return m_line < other.m_line || (m_line == other.m_line && m_column < other.m_column); } - bool operator>(TextPosition const& other) const { return *this != other && !(*this < other); } - bool operator>=(TextPosition const& other) const { return *this > other || (*this == other); } - -private: - size_t m_line { 0xffffffff }; - size_t m_column { 0xffffffff }; -}; +using TextPosition = Syntax::TextPosition; } - -template<> -struct AK::Formatter : AK::Formatter { - ErrorOr format(FormatBuilder& builder, GUI::TextPosition const& value) - { - if (value.is_valid()) - return Formatter::format(builder, "({},{})"sv, value.line(), value.column()); - - return builder.put_string("GUI::TextPosition(Invalid)"sv); - } -}; diff --git a/Userland/Libraries/LibGUI/TextRange.h b/Userland/Libraries/LibGUI/TextRange.h index b8065e670d..082f3c0269 100644 --- a/Userland/Libraries/LibGUI/TextRange.h +++ b/Userland/Libraries/LibGUI/TextRange.h @@ -1,80 +1,15 @@ /* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2022, the SerenityOS developers. + * Copyright (c) 2023, Ali Mohammad Pur * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once -#include +#include namespace GUI { -class TextRange { -public: - TextRange() = default; - TextRange(TextPosition const& start, TextPosition const& end) - : m_start(start) - , m_end(end) - { - } - - bool is_valid() const { return m_start.is_valid() && m_end.is_valid() && m_start != m_end; } - void clear() - { - m_start = {}; - m_end = {}; - } - - TextPosition& start() { return m_start; } - TextPosition& end() { return m_end; } - TextPosition const& start() const { return m_start; } - TextPosition const& end() const { return m_end; } - - size_t line_count() const { return normalized_end().line() - normalized_start().line() + 1; } - - TextRange normalized() const { return TextRange(normalized_start(), normalized_end()); } - - void set_start(TextPosition const& position) { m_start = position; } - void set_end(TextPosition const& position) { m_end = position; } - - void set(TextPosition const& start, TextPosition const& end) - { - m_start = start; - m_end = end; - } - - bool operator==(TextRange const& other) const - { - return m_start == other.m_start && m_end == other.m_end; - } - - bool contains(TextPosition const& position) const - { - if (!(position.line() > m_start.line() || (position.line() == m_start.line() && position.column() >= m_start.column()))) - return false; - if (!(position.line() < m_end.line() || (position.line() == m_end.line() && position.column() <= m_end.column()))) - return false; - return true; - } - -private: - TextPosition normalized_start() const { return m_start < m_end ? m_start : m_end; } - TextPosition normalized_end() const { return m_start < m_end ? m_end : m_start; } - - TextPosition m_start {}; - TextPosition m_end {}; -}; +using TextRange = Syntax::TextRange; } - -template<> -struct AK::Formatter : AK::Formatter { - ErrorOr format(FormatBuilder& builder, GUI::TextRange const& value) - { - if (value.is_valid()) - return Formatter::format(builder, "{}-{}"sv, value.start(), value.end()); - return builder.put_string("GUI::TextRange(Invalid)"sv); - } -}; diff --git a/Userland/Libraries/LibJS/SyntaxHighlighter.cpp b/Userland/Libraries/LibJS/SyntaxHighlighter.cpp index f7f0529f50..e4ec8bb0f2 100644 --- a/Userland/Libraries/LibJS/SyntaxHighlighter.cpp +++ b/Userland/Libraries/LibJS/SyntaxHighlighter.cpp @@ -54,10 +54,10 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) Lexer lexer(text); - Vector spans; - Vector folding_regions; - GUI::TextPosition position { 0, 0 }; - GUI::TextPosition start { 0, 0 }; + Vector spans; + Vector folding_regions; + Syntax::TextPosition position { 0, 0 }; + Syntax::TextPosition start { 0, 0 }; auto advance_position = [&position](char ch) { if (ch == '\n') { @@ -75,7 +75,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) for (size_t i = 0; i < str.length(); ++i) advance_position(str[i]); - GUI::TextDocumentSpan span; + Syntax::TextDocumentSpan span; span.range.set_start(start); span.range.set_end({ position.line(), position.column() }); auto type = is_trivia ? TokenType::Invalid : token.type(); @@ -94,7 +94,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) struct TokenData { Token token; - GUI::TextRange range; + Syntax::TextRange range; }; Vector folding_region_start_tokens; @@ -115,7 +115,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) } else if (token.type() == TokenType::CurlyClose) { if (!folding_region_start_tokens.is_empty()) { auto curly_open = folding_region_start_tokens.take_last(); - GUI::TextDocumentFoldingRegion region; + Syntax::TextDocumentFoldingRegion region; region.range.set_start(curly_open.range.end()); region.range.set_end(token_start_position); folding_regions.append(region); diff --git a/Userland/Libraries/LibMarkdown/SyntaxHighlighter.cpp b/Userland/Libraries/LibMarkdown/SyntaxHighlighter.cpp index 93a3ffc32a..16380e8ba0 100644 --- a/Userland/Libraries/LibMarkdown/SyntaxHighlighter.cpp +++ b/Userland/Libraries/LibMarkdown/SyntaxHighlighter.cpp @@ -33,13 +33,13 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) { auto text = m_client->get_text(); - Vector spans; + Vector spans; - auto append_header = [&](GUI::TextRange const& range) { + auto append_header = [&](Syntax::TextRange const& range) { Gfx::TextAttributes attributes; attributes.color = palette.base_text(); attributes.bold = true; - GUI::TextDocumentSpan span { + Syntax::TextDocumentSpan span { .range = range, .attributes = attributes, .data = static_cast(Token::Header), @@ -48,10 +48,10 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) spans.append(span); }; - auto append_code_block = [&](GUI::TextRange const& range) { + auto append_code_block = [&](Syntax::TextRange const& range) { Gfx::TextAttributes attributes; attributes.color = palette.syntax_string(); - GUI::TextDocumentSpan span { + Syntax::TextDocumentSpan span { .range = range, .attributes = attributes, .data = static_cast(Token::Code), diff --git a/Userland/Libraries/LibSQL/AST/SyntaxHighlighter.cpp b/Userland/Libraries/LibSQL/AST/SyntaxHighlighter.cpp index b6898080d5..2002267ec3 100644 --- a/Userland/Libraries/LibSQL/AST/SyntaxHighlighter.cpp +++ b/Userland/Libraries/LibSQL/AST/SyntaxHighlighter.cpp @@ -46,12 +46,12 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) Lexer lexer(text); - Vector spans; + Vector spans; auto append_token = [&](Token const& token) { if (token.value().is_empty()) return; - GUI::TextDocumentSpan span; + Syntax::TextDocumentSpan span; span.range.set_start({ token.start_position().line - 1, token.start_position().column - 1 }); span.range.set_end({ token.end_position().line - 1, token.end_position().column - 1 }); span.attributes = style_for_token_type(palette, token.type()); diff --git a/Userland/Libraries/LibSyntax/CMakeLists.txt b/Userland/Libraries/LibSyntax/CMakeLists.txt index 9708cc0850..1ae97273c1 100644 --- a/Userland/Libraries/LibSyntax/CMakeLists.txt +++ b/Userland/Libraries/LibSyntax/CMakeLists.txt @@ -1,4 +1,5 @@ set(SOURCES + Document.cpp Highlighter.cpp Language.cpp ) diff --git a/Userland/Libraries/LibSyntax/Document.cpp b/Userland/Libraries/LibSyntax/Document.cpp new file mode 100644 index 0000000000..08a1f5c2d5 --- /dev/null +++ b/Userland/Libraries/LibSyntax/Document.cpp @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2023, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Syntax { + +size_t TextDocumentLine::first_non_whitespace_column() const +{ + for (size_t i = 0; i < length(); ++i) { + auto code_point = code_points()[i]; + if (!is_ascii_space(code_point)) + return i; + } + return length(); +} + +Optional TextDocumentLine::last_non_whitespace_column() const +{ + for (ssize_t i = length() - 1; i >= 0; --i) { + auto code_point = code_points()[i]; + if (!is_ascii_space(code_point)) + return i; + } + return {}; +} + +bool TextDocumentLine::ends_in_whitespace() const +{ + if (!length()) + return false; + return is_ascii_space(code_points()[length() - 1]); +} + +bool TextDocumentLine::can_select() const +{ + if (is_empty()) + return false; + for (size_t i = 0; i < length(); ++i) { + auto code_point = code_points()[i]; + if (code_point != '\n' && code_point != '\r' && code_point != '\f' && code_point != '\v') + return true; + } + return false; +} + +size_t TextDocumentLine::leading_spaces() const +{ + size_t count = 0; + for (; count < m_text.size(); ++count) { + if (m_text[count] != ' ') { + break; + } + } + return count; +} + +DeprecatedString TextDocumentLine::to_utf8() const +{ + StringBuilder builder; + builder.append(view()); + return builder.to_deprecated_string(); +} + +TextDocumentLine::TextDocumentLine(Document& document) +{ + clear(document); +} + +TextDocumentLine::TextDocumentLine(Document& document, StringView text) +{ + set_text(document, text); +} + +void TextDocumentLine::clear(Document& document) +{ + m_text.clear(); + document.update_views({}); +} + +void TextDocumentLine::set_text(Document& document, Vector const text) +{ + m_text = move(text); + document.update_views({}); +} + +bool TextDocumentLine::set_text(Document& document, StringView text) +{ + if (text.is_empty()) { + clear(document); + return true; + } + m_text.clear(); + Utf8View utf8_view(text); + if (!utf8_view.validate()) { + return false; + } + for (auto code_point : utf8_view) + m_text.append(code_point); + document.update_views({}); + return true; +} + +void TextDocumentLine::append(Document& document, u32 const* code_points, size_t length) +{ + if (length == 0) + return; + m_text.append(code_points, length); + document.update_views({}); +} + +void TextDocumentLine::append(Document& document, u32 code_point) +{ + insert(document, length(), code_point); +} + +void TextDocumentLine::prepend(Document& document, u32 code_point) +{ + insert(document, 0, code_point); +} + +void TextDocumentLine::insert(Document& document, size_t index, u32 code_point) +{ + if (index == length()) { + m_text.append(code_point); + } else { + m_text.insert(index, code_point); + } + document.update_views({}); +} + +void TextDocumentLine::remove(Document& document, size_t index) +{ + if (index == length()) { + m_text.take_last(); + } else { + m_text.remove(index); + } + document.update_views({}); +} + +void TextDocumentLine::remove_range(Document& document, size_t start, size_t length) +{ + VERIFY(length <= m_text.size()); + + Vector new_data; + new_data.ensure_capacity(m_text.size() - length); + for (size_t i = 0; i < start; ++i) + new_data.append(m_text[i]); + for (size_t i = (start + length); i < m_text.size(); ++i) + new_data.append(m_text[i]); + m_text = move(new_data); + document.update_views({}); +} + +void TextDocumentLine::keep_range(Document& document, size_t start_index, size_t length) +{ + VERIFY(start_index + length < m_text.size()); + + Vector new_data; + new_data.ensure_capacity(m_text.size()); + for (size_t i = start_index; i <= (start_index + length); i++) + new_data.append(m_text[i]); + + m_text = move(new_data); + document.update_views({}); +} + +void TextDocumentLine::truncate(Document& document, size_t length) +{ + m_text.resize(length); + document.update_views({}); +} + +TextDocumentSpan const* Document::span_at(TextPosition const& position) const +{ + for (auto& span : m_spans) { + if (span.range.contains(position)) + return &span; + } + return nullptr; +} + +void Document::set_spans(u32 span_collection_index, Vector spans) +{ + m_span_collections.set(span_collection_index, move(spans)); + merge_span_collections(); +} + +struct SpanAndCollectionIndex { + TextDocumentSpan span; + u32 collection_index { 0 }; +}; + +void Document::merge_span_collections() +{ + Vector sorted_spans; + auto collection_indices = m_span_collections.keys(); + quick_sort(collection_indices); + + for (auto collection_index : collection_indices) { + auto spans = m_span_collections.get(collection_index).value(); + for (auto span : spans) { + sorted_spans.append({ move(span), collection_index }); + } + } + + quick_sort(sorted_spans, [](SpanAndCollectionIndex const& a, SpanAndCollectionIndex const& b) { + if (a.span.range.start() == b.span.range.start()) { + return a.collection_index < b.collection_index; + } + return a.span.range.start() < b.span.range.start(); + }); + + // The end of the TextRanges of spans are non-inclusive, i.e span range = [X,y). + // This transforms the span's range to be inclusive, i.e [X,Y]. + auto adjust_end = [](TextDocumentSpan span) -> TextDocumentSpan { + span.range.set_end({ span.range.end().line(), span.range.end().column() == 0 ? 0 : span.range.end().column() - 1 }); + return span; + }; + + Vector merged_spans; + for (auto& span_and_collection_index : sorted_spans) { + if (merged_spans.is_empty()) { + merged_spans.append(span_and_collection_index); + continue; + } + + auto const& span = span_and_collection_index.span; + auto last_span_and_collection_index = merged_spans.last(); + auto const& last_span = last_span_and_collection_index.span; + + if (adjust_end(span).range.start() > adjust_end(last_span).range.end()) { + // Current span does not intersect with previous one, can simply append to merged list. + merged_spans.append(span_and_collection_index); + continue; + } + merged_spans.take_last(); + + if (span.range.start() > last_span.range.start()) { + SpanAndCollectionIndex first_part = last_span_and_collection_index; + first_part.span.range.set_end(span.range.start()); + merged_spans.append(move(first_part)); + } + + SpanAndCollectionIndex merged_span; + merged_span.collection_index = span_and_collection_index.collection_index; + merged_span.span.range = { span.range.start(), min(span.range.end(), last_span.range.end()) }; + merged_span.span.is_skippable = span.is_skippable | last_span.is_skippable; + merged_span.span.data = span.data ? span.data : last_span.data; + merged_span.span.attributes.color = span_and_collection_index.collection_index > last_span_and_collection_index.collection_index ? span.attributes.color : last_span.attributes.color; + merged_span.span.attributes.bold = span.attributes.bold | last_span.attributes.bold; + merged_span.span.attributes.background_color = span.attributes.background_color.has_value() ? span.attributes.background_color.value() : last_span.attributes.background_color; + merged_span.span.attributes.underline_color = span.attributes.underline_color.has_value() ? span.attributes.underline_color.value() : last_span.attributes.underline_color; + merged_span.span.attributes.underline_style = span.attributes.underline_style.has_value() ? span.attributes.underline_style : last_span.attributes.underline_style; + merged_spans.append(move(merged_span)); + + if (span.range.end() == last_span.range.end()) + continue; + + if (span.range.end() > last_span.range.end()) { + SpanAndCollectionIndex last_part = span_and_collection_index; + last_part.span.range.set_start(last_span.range.end()); + merged_spans.append(move(last_part)); + continue; + } + + SpanAndCollectionIndex last_part = last_span_and_collection_index; + last_part.span.range.set_start(span.range.end()); + merged_spans.append(move(last_part)); + } + + m_spans.clear(); + TextDocumentSpan previous_span { .range = { TextPosition(0, 0), TextPosition(0, 0) }, .attributes = {} }; + for (auto span : merged_spans) { + // Validate spans + if (!span.span.range.is_valid()) { + dbgln_if(TEXTEDITOR_DEBUG, "Invalid span {} => ignoring", span.span.range); + continue; + } + if (span.span.range.end() < span.span.range.start()) { + dbgln_if(TEXTEDITOR_DEBUG, "Span {} has negative length => ignoring", span.span.range); + continue; + } + if (span.span.range.end() < previous_span.range.start()) { + dbgln_if(TEXTEDITOR_DEBUG, "Spans not sorted (Span {} ends before previous span {}) => ignoring", span.span.range, previous_span.range); + continue; + } + if (span.span.range.start() < previous_span.range.end()) { + dbgln_if(TEXTEDITOR_DEBUG, "Span {} overlaps previous span {} => ignoring", span.span.range, previous_span.range); + continue; + } + + previous_span = span.span; + m_spans.append(move(span.span)); + } +} + +void Document::set_folding_regions(Vector folding_regions) +{ + // Remove any regions that don't span at least 3 lines. + // Currently, we can't do anything useful with them, and our implementation gets very confused by + // single-line regions, so drop them. + folding_regions.remove_all_matching([](TextDocumentFoldingRegion const& region) { + return region.range.line_count() < 3; + }); + + quick_sort(folding_regions, [](TextDocumentFoldingRegion const& a, TextDocumentFoldingRegion const& b) { + return a.range.start() < b.range.start(); + }); + + for (auto& folding_region : folding_regions) { + folding_region.line_ptr = &line(folding_region.range.start().line()); + + // Map the new folding region to an old one, to preserve which regions were folded. + // FIXME: This is O(n*n). + for (auto const& existing_folding_region : m_folding_regions) { + // We treat two folding regions as the same if they start on the same TextDocumentLine, + // and have the same line count. The actual line *numbers* might change, but the pointer + // and count should not. + if (existing_folding_region.line_ptr + && existing_folding_region.line_ptr == folding_region.line_ptr + && existing_folding_region.range.line_count() == folding_region.range.line_count()) { + folding_region.is_folded = existing_folding_region.is_folded; + break; + } + } + } + + // FIXME: Remove any regions that partially overlap another region, since these are invalid. + + m_folding_regions = move(folding_regions); + + if constexpr (TEXTEDITOR_DEBUG) { + dbgln("Document got {} fold regions:", m_folding_regions.size()); + for (auto const& item : m_folding_regions) { + dbgln("- {} (ptr: {:p}, folded: {})", item.range, item.line_ptr, item.is_folded); + } + } +} + +Optional Document::folding_region_starting_on_line(size_t line) +{ + return m_folding_regions.first_matching([line](auto& region) { + return region.range.start().line() == line; + }); +} + +bool Document::line_is_visible(size_t line) const +{ + // FIXME: line_is_visible() gets called a lot. + // We could avoid a lot of repeated work if we saved this state on the TextDocumentLine. + return !any_of(m_folding_regions, [line](auto& region) { + return region.is_folded + && line > region.range.start().line() + && line < region.range.end().line(); + }); +} + +Vector Document::currently_folded_regions() const +{ + Vector folded_regions; + + for (auto& region : m_folding_regions) { + if (region.is_folded) { + // Only add this region if it's not contained within a previous folded region. + // Because regions are sorted by their start position, and regions cannot partially overlap, + // we can just see if it starts inside the last region we appended. + if (!folded_regions.is_empty() && folded_regions.last().range.contains(region.range.start())) + continue; + + folded_regions.append(region); + } + } + + return folded_regions; +} + +} diff --git a/Userland/Libraries/LibSyntax/Document.h b/Userland/Libraries/LibSyntax/Document.h new file mode 100644 index 0000000000..0e46088fe4 --- /dev/null +++ b/Userland/Libraries/LibSyntax/Document.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Syntax { + +struct TextDocumentSpan { + TextRange range; + Gfx::TextAttributes attributes; + u64 data { 0 }; + bool is_skippable { false }; +}; + +struct TextDocumentFoldingRegion { + TextRange range; + bool is_folded { false }; + // This pointer is only used to identify that two TDFRs are the same. + RawPtr line_ptr; +}; + +class TextDocumentLine { +public: + explicit TextDocumentLine(Document&); + explicit TextDocumentLine(Document&, StringView); + + DeprecatedString to_utf8() const; + + Utf32View view() const { return { code_points(), length() }; } + u32 const* code_points() const { return m_text.data(); } + size_t length() const { return m_text.size(); } + bool set_text(Document&, StringView); + void set_text(Document&, Vector); + void append(Document&, u32); + void prepend(Document&, u32); + void insert(Document&, size_t index, u32); + void remove(Document&, size_t index); + void append(Document&, u32 const*, size_t); + void truncate(Document&, size_t length); + void clear(Document&); + void remove_range(Document&, size_t start, size_t length); + void keep_range(Document&, size_t start_index, size_t end_index); + + size_t first_non_whitespace_column() const; + Optional last_non_whitespace_column() const; + bool ends_in_whitespace() const; + bool can_select() const; + bool is_empty() const { return length() == 0; } + size_t leading_spaces() const; + +private: + // NOTE: This vector is null terminated. + Vector m_text; +}; + +class Document : public RefCounted { +public: + Document() = default; + virtual ~Document() = default; + + void set_spans(u32 span_collection_index, Vector spans); + bool has_spans() const { return !m_spans.is_empty(); } + Vector& spans() { return m_spans; } + Vector const& spans() const { return m_spans; } + void set_span_at_index(size_t index, TextDocumentSpan span) { m_spans[index] = move(span); } + TextDocumentSpan const* span_at(TextPosition const&) const; + + void set_folding_regions(Vector); + bool has_folding_regions() const { return !m_folding_regions.is_empty(); } + Vector& folding_regions() { return m_folding_regions; } + Vector const& folding_regions() const { return m_folding_regions; } + Optional folding_region_starting_on_line(size_t line); + // Returns all folded FoldingRegions that are not contained inside another folded region. + Vector currently_folded_regions() const; + // Returns true if any part of the line is currently visible. (Not inside a folded FoldingRegion.) + bool line_is_visible(size_t line) const; + + virtual TextDocumentLine const& line(size_t line_index) const = 0; + virtual TextDocumentLine& line(size_t line_index) = 0; + + virtual void update_views(Badge) = 0; + +protected: + HashMap> m_span_collections; + Vector m_spans; + Vector m_folding_regions; + +private: + void merge_span_collections(); +}; + +} diff --git a/Userland/Libraries/LibSyntax/Forward.h b/Userland/Libraries/LibSyntax/Forward.h index e34081d921..363140de16 100644 --- a/Userland/Libraries/LibSyntax/Forward.h +++ b/Userland/Libraries/LibSyntax/Forward.h @@ -8,7 +8,10 @@ namespace Syntax { +class Document; class Highlighter; class HighlighterClient; +class TextPosition; +class TextRange; } diff --git a/Userland/Libraries/LibSyntax/Highlighter.h b/Userland/Libraries/LibSyntax/Highlighter.h index 6479505dca..b9ac566679 100644 --- a/Userland/Libraries/LibSyntax/Highlighter.h +++ b/Userland/Libraries/LibSyntax/Highlighter.h @@ -8,8 +8,8 @@ #include #include -#include #include +#include #include #include @@ -61,7 +61,7 @@ protected: struct BuddySpan { int index { -1 }; - GUI::TextDocumentSpan span_backup; + TextDocumentSpan span_backup; }; bool m_has_brace_buddies { false }; @@ -71,7 +71,7 @@ protected: class ProxyHighlighterClient final : public Syntax::HighlighterClient { public: - ProxyHighlighterClient(Syntax::HighlighterClient& client, GUI::TextPosition start, u64 nested_kind_start_value, StringView source) + ProxyHighlighterClient(Syntax::HighlighterClient& client, TextPosition start, u64 nested_kind_start_value, StringView source) : m_document(client.get_document()) , m_text(source) , m_start(start) @@ -79,9 +79,9 @@ public: { } - Vector corrected_spans() const + Vector corrected_spans() const { - Vector spans { m_spans }; + Vector spans { m_spans }; for (auto& entry : spans) { entry.range.start() = { entry.range.start().line() + m_start.line(), @@ -98,9 +98,9 @@ public: return spans; } - Vector corrected_folding_regions() const + Vector corrected_folding_regions() const { - Vector folding_regions { m_folding_regions }; + Vector folding_regions { m_folding_regions }; for (auto& entry : folding_regions) { entry.range.start() = { entry.range.start().line() + m_start.line(), @@ -125,24 +125,24 @@ public: } private: - virtual Vector const& spans() const override { return m_spans; } - virtual void set_span_at_index(size_t index, GUI::TextDocumentSpan span) override { m_spans.at(index) = move(span); } + virtual Vector const& spans() const override { return m_spans; } + virtual void set_span_at_index(size_t index, TextDocumentSpan span) override { m_spans.at(index) = move(span); } - virtual Vector& folding_regions() override { return m_folding_regions; } - virtual Vector const& folding_regions() const override { return m_folding_regions; } + virtual Vector& folding_regions() override { return m_folding_regions; } + virtual Vector const& folding_regions() const override { return m_folding_regions; } virtual DeprecatedString highlighter_did_request_text() const override { return m_text; } virtual void highlighter_did_request_update() override { } - virtual GUI::TextDocument& highlighter_did_request_document() override { return m_document; } - virtual GUI::TextPosition highlighter_did_request_cursor() const override { return {}; } - virtual void highlighter_did_set_spans(Vector spans) override { m_spans = move(spans); } - virtual void highlighter_did_set_folding_regions(Vector folding_regions) override { m_folding_regions = folding_regions; } + virtual Document& highlighter_did_request_document() override { return m_document; } + virtual TextPosition highlighter_did_request_cursor() const override { return {}; } + virtual void highlighter_did_set_spans(Vector spans) override { m_spans = move(spans); } + virtual void highlighter_did_set_folding_regions(Vector folding_regions) override { m_folding_regions = folding_regions; } - Vector m_spans; - Vector m_folding_regions; - GUI::TextDocument& m_document; + Vector m_spans; + Vector m_folding_regions; + Document& m_document; StringView m_text; - GUI::TextPosition m_start; + TextPosition m_start; u64 m_nested_kind_start_value { 0 }; }; diff --git a/Userland/Libraries/LibSyntax/HighlighterClient.h b/Userland/Libraries/LibSyntax/HighlighterClient.h index e86bcb3769..c524d6b60e 100644 --- a/Userland/Libraries/LibSyntax/HighlighterClient.h +++ b/Userland/Libraries/LibSyntax/HighlighterClient.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2021, Andreas Kling + * Copyright (c) 2023, Ali Mohammad Pur * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,8 +9,8 @@ #include #include -#include -#include +#include +#include namespace Syntax { @@ -17,27 +18,27 @@ class HighlighterClient { public: virtual ~HighlighterClient() = default; - virtual Vector const& spans() const = 0; - virtual void set_span_at_index(size_t index, GUI::TextDocumentSpan span) = 0; + virtual Vector const& spans() const = 0; + virtual void set_span_at_index(size_t index, TextDocumentSpan span) = 0; virtual void clear_spans() { do_set_spans({}); } - virtual Vector& folding_regions() = 0; - virtual Vector const& folding_regions() const = 0; + virtual Vector& folding_regions() = 0; + virtual Vector const& folding_regions() const = 0; virtual DeprecatedString highlighter_did_request_text() const = 0; virtual void highlighter_did_request_update() = 0; - virtual GUI::TextDocument& highlighter_did_request_document() = 0; - virtual GUI::TextPosition highlighter_did_request_cursor() const = 0; - virtual void highlighter_did_set_spans(Vector) = 0; - virtual void highlighter_did_set_folding_regions(Vector) = 0; + virtual Document& highlighter_did_request_document() = 0; + virtual TextPosition highlighter_did_request_cursor() const = 0; + virtual void highlighter_did_set_spans(Vector) = 0; + virtual void highlighter_did_set_folding_regions(Vector) = 0; - void do_set_spans(Vector spans) { highlighter_did_set_spans(move(spans)); } - void do_set_folding_regions(Vector folding_regions) { highlighter_did_set_folding_regions(move(folding_regions)); } + void do_set_spans(Vector spans) { highlighter_did_set_spans(move(spans)); } + void do_set_folding_regions(Vector folding_regions) { highlighter_did_set_folding_regions(move(folding_regions)); } void do_update() { highlighter_did_request_update(); } DeprecatedString get_text() const { return highlighter_did_request_text(); } - GUI::TextDocument& get_document() { return highlighter_did_request_document(); } - GUI::TextPosition get_cursor() const { return highlighter_did_request_cursor(); } + Document& get_document() { return highlighter_did_request_document(); } + TextPosition get_cursor() const { return highlighter_did_request_cursor(); } static constexpr auto span_collection_index = 0; }; diff --git a/Userland/Libraries/LibSyntax/TextPosition.h b/Userland/Libraries/LibSyntax/TextPosition.h new file mode 100644 index 0000000000..79b652741c --- /dev/null +++ b/Userland/Libraries/LibSyntax/TextPosition.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2023, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Syntax { + +class TextPosition { +public: + TextPosition() = default; + TextPosition(size_t line, size_t column) + : m_line(line) + , m_column(column) + { + } + + bool is_valid() const { return m_line != 0xffffffffu && m_column != 0xffffffffu; } + + size_t line() const { return m_line; } + size_t column() const { return m_column; } + + void set_line(size_t line) { m_line = line; } + void set_column(size_t column) { m_column = column; } + + bool operator==(TextPosition const& other) const { return m_line == other.m_line && m_column == other.m_column; } + bool operator!=(TextPosition const& other) const { return m_line != other.m_line || m_column != other.m_column; } + bool operator<(TextPosition const& other) const { return m_line < other.m_line || (m_line == other.m_line && m_column < other.m_column); } + bool operator>(TextPosition const& other) const { return *this != other && !(*this < other); } + bool operator>=(TextPosition const& other) const { return *this > other || (*this == other); } + +private: + size_t m_line { 0xffffffff }; + size_t m_column { 0xffffffff }; +}; + +} + +template<> +struct AK::Formatter : AK::Formatter { + ErrorOr format(FormatBuilder& builder, Syntax::TextPosition const& value) + { + if (value.is_valid()) + return Formatter::format(builder, "({},{})"sv, value.line(), value.column()); + + return builder.put_string("TextPosition(Invalid)"sv); + } +}; diff --git a/Userland/Libraries/LibSyntax/TextRange.h b/Userland/Libraries/LibSyntax/TextRange.h new file mode 100644 index 0000000000..265004ebe5 --- /dev/null +++ b/Userland/Libraries/LibSyntax/TextRange.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2022-2023, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Syntax { + +class TextRange { +public: + TextRange() = default; + TextRange(TextPosition const& start, TextPosition const& end) + : m_start(start) + , m_end(end) + { + } + + bool is_valid() const { return m_start.is_valid() && m_end.is_valid() && m_start != m_end; } + void clear() + { + m_start = {}; + m_end = {}; + } + + TextPosition& start() { return m_start; } + TextPosition& end() { return m_end; } + TextPosition const& start() const { return m_start; } + TextPosition const& end() const { return m_end; } + + size_t line_count() const { return normalized_end().line() - normalized_start().line() + 1; } + + TextRange normalized() const { return TextRange(normalized_start(), normalized_end()); } + + void set_start(TextPosition const& position) { m_start = position; } + void set_end(TextPosition const& position) { m_end = position; } + + void set(TextPosition const& start, TextPosition const& end) + { + m_start = start; + m_end = end; + } + + bool operator==(TextRange const& other) const + { + return m_start == other.m_start && m_end == other.m_end; + } + + bool contains(TextPosition const& position) const + { + if (position.line() <= m_start.line() && (position.line() != m_start.line() || position.column() < m_start.column())) + return false; + if (position.line() >= m_end.line() && (position.line() != m_end.line() || position.column() > m_end.column())) + return false; + return true; + } + +private: + TextPosition normalized_start() const { return m_start < m_end ? m_start : m_end; } + TextPosition normalized_end() const { return m_start < m_end ? m_end : m_start; } + + TextPosition m_start {}; + TextPosition m_end {}; +}; + +} + +template<> +struct AK::Formatter : AK::Formatter { + ErrorOr format(FormatBuilder& builder, Syntax::TextRange const& value) + { + if (value.is_valid()) + return Formatter::format(builder, "{}-{}"sv, value.start(), value.end()); + return builder.put_string("TextRange(Invalid)"sv); + } +}; diff --git a/Userland/Libraries/LibWeb/CSS/SyntaxHighlighter/SyntaxHighlighter.cpp b/Userland/Libraries/LibWeb/CSS/SyntaxHighlighter/SyntaxHighlighter.cpp index 88f67cabb8..cb993b3a20 100644 --- a/Userland/Libraries/LibWeb/CSS/SyntaxHighlighter/SyntaxHighlighter.cpp +++ b/Userland/Libraries/LibWeb/CSS/SyntaxHighlighter/SyntaxHighlighter.cpp @@ -26,8 +26,8 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) auto text = m_client->get_text(); Vector folding_region_start_tokens; - Vector folding_regions; - Vector spans; + Vector folding_regions; + Vector spans; auto highlight = [&](auto start_line, auto start_column, auto end_line, auto end_column, Gfx::TextAttributes attributes, CSS::Parser::Token::Type type) { if (start_line > end_line || (start_line == end_line && start_column >= end_column)) { @@ -36,7 +36,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) } dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(CSS::SyntaxHighlighter) highlighting ({}-{}) to ({}-{}) with color {}", start_line, start_column, end_line, end_column, attributes.color); spans.empend( - GUI::TextRange { + Syntax::TextRange { { start_line, start_column }, { end_line, end_column }, }, @@ -55,7 +55,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) } else if (token.is(Parser::Token::Type::CloseCurly)) { if (!folding_region_start_tokens.is_empty()) { auto start_token = folding_region_start_tokens.take_last(); - GUI::TextDocumentFoldingRegion folding_region; + Syntax::TextDocumentFoldingRegion folding_region; folding_region.range.set_start({ start_token.end_position().line, start_token.end_position().column }); folding_region.range.set_end({ token.start_position().line, token.start_position().column }); folding_regions.append(move(folding_region)); diff --git a/Userland/Libraries/LibWeb/HTML/SyntaxHighlighter/SyntaxHighlighter.cpp b/Userland/Libraries/LibWeb/HTML/SyntaxHighlighter/SyntaxHighlighter.cpp index 0fb3663922..7b3a412a70 100644 --- a/Userland/Libraries/LibWeb/HTML/SyntaxHighlighter/SyntaxHighlighter.cpp +++ b/Userland/Libraries/LibWeb/HTML/SyntaxHighlighter/SyntaxHighlighter.cpp @@ -42,8 +42,8 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) clear_nested_token_pairs(); // FIXME: Add folding regions for start and end tags. - Vector folding_regions; - Vector spans; + Vector folding_regions; + Vector spans; auto highlight = [&](auto start_line, auto start_column, auto end_line, auto end_column, Gfx::TextAttributes attributes, AugmentedTokenKind kind) { if (start_line > end_line || (start_line == end_line && start_column >= end_column)) { dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(HTML::SyntaxHighlighter) discarding ({}-{}) to ({}-{}) because it has zero or negative length", start_line, start_column, end_line, end_column); @@ -51,7 +51,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) } dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(HTML::SyntaxHighlighter) highlighting ({}-{}) to ({}-{}) with color {}", start_line, start_column, end_line, end_column, attributes.color); spans.empend( - GUI::TextRange { + Syntax::TextRange { { start_line, start_column }, { end_line, end_column }, }, @@ -67,7 +67,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) CSS, } state { State::HTML }; StringBuilder substring_builder; - GUI::TextPosition substring_start_position; + Syntax::TextPosition substring_start_position; for (;;) { auto token = tokenizer.next_token(); @@ -141,7 +141,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) { palette.syntax_comment(), {} }, AugmentedTokenKind::Comment); - GUI::TextDocumentFoldingRegion region; + Syntax::TextDocumentFoldingRegion region; region.range.set_start({ token->start_position().line, token->start_position().column + comment_prefix()->length() }); region.range.set_end({ token->end_position().line, token->end_position().column - comment_suffix()->length() }); folding_regions.append(move(region)); diff --git a/Userland/Shell/SyntaxHighlighter.cpp b/Userland/Shell/SyntaxHighlighter.cpp index eb4f3f6bcb..4bd5c08321 100644 --- a/Userland/Shell/SyntaxHighlighter.cpp +++ b/Userland/Shell/SyntaxHighlighter.cpp @@ -7,9 +7,9 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -24,7 +24,7 @@ enum class AugmentedTokenKind : u32 { class HighlightVisitor : public AST::NodeVisitor { public: - HighlightVisitor(Vector& spans, Gfx::Palette const& palette, const GUI::TextDocument& document) + HighlightVisitor(Vector& spans, Gfx::Palette const& palette, Syntax::Document const& document) : m_spans(spans) , m_palette(palette) , m_document(document) @@ -32,7 +32,7 @@ public: } private: - AST::Position::Line offset_line(const AST::Position::Line& line, size_t offset) + AST::Position::Line offset_line(AST::Position::Line const& line, size_t offset) { // We need to look at the line(s) above. AST::Position::Line new_line { line }; @@ -52,20 +52,20 @@ private: return new_line; } - void set_offset_range_end(GUI::TextRange& range, const AST::Position::Line& line, size_t offset = 0) + void set_offset_range_end(Syntax::TextRange& range, AST::Position::Line const& line, size_t offset = 0) { auto new_line = offset_line(line, offset); range.set_end({ new_line.line_number, new_line.line_column }); } - void set_offset_range_start(GUI::TextRange& range, const AST::Position::Line& line, size_t offset = 0) + void set_offset_range_start(Syntax::TextRange& range, AST::Position::Line const& line, size_t offset = 0) { auto new_line = offset_line(line, offset); range.set_start({ new_line.line_number, new_line.line_column }); } - GUI::TextDocumentSpan& span_for_node(const AST::Node* node) + Syntax::TextDocumentSpan& span_for_node(AST::Node const* node) { - GUI::TextDocumentSpan span; + Syntax::TextDocumentSpan span; set_offset_range_start(span.range, node->position().start_line); set_offset_range_end(span.range, node->position().end_line); span.data = static_cast(node->kind()); @@ -75,7 +75,7 @@ private: return m_spans.last(); } - virtual void visit(const AST::PathRedirectionNode* node) override + virtual void visit(AST::PathRedirectionNode const* node) override { if (node->path()->is_bareword()) { auto& span = span_for_node(node->path()); @@ -85,7 +85,7 @@ private: NodeVisitor::visit(node); } } - virtual void visit(const AST::And* node) override + virtual void visit(AST::And const* node) override { { ScopedValueRollback first_in_command { m_is_first_in_command }; @@ -102,11 +102,11 @@ private: span.attributes.color = m_palette.syntax_punctuation(); span.attributes.bold = true; } - virtual void visit(const AST::ListConcatenate* node) override + virtual void visit(AST::ListConcatenate const* node) override { NodeVisitor::visit(node); } - virtual void visit(const AST::Background* node) override + virtual void visit(AST::Background const* node) override { NodeVisitor::visit(node); @@ -115,11 +115,11 @@ private: span.attributes.color = m_palette.syntax_punctuation(); span.attributes.bold = true; } - virtual void visit(const AST::BraceExpansion* node) override + virtual void visit(AST::BraceExpansion const* node) override { NodeVisitor::visit(node); } - virtual void visit(const AST::BarewordLiteral* node) override + virtual void visit(AST::BarewordLiteral const* node) override { NodeVisitor::visit(node); @@ -134,11 +134,11 @@ private: span.attributes.color = m_palette.base_text(); } } - virtual void visit(const AST::CastToCommand* node) override + virtual void visit(AST::CastToCommand const* node) override { NodeVisitor::visit(node); } - virtual void visit(const AST::CastToList* node) override + virtual void visit(AST::CastToList const* node) override { NodeVisitor::visit(node); @@ -152,29 +152,29 @@ private: set_offset_range_start(end_span.range, node->position().end_line, 1); end_span.data = static_cast(AugmentedTokenKind::CloseParen); } - virtual void visit(const AST::CloseFdRedirection* node) override + virtual void visit(AST::CloseFdRedirection const* node) override { NodeVisitor::visit(node); } - virtual void visit(const AST::CommandLiteral* node) override + virtual void visit(AST::CommandLiteral const* node) override { NodeVisitor::visit(node); } - virtual void visit(const AST::Comment* node) override + virtual void visit(AST::Comment const* node) override { NodeVisitor::visit(node); auto& span = span_for_node(node); span.attributes.color = m_palette.syntax_comment(); } - virtual void visit(const AST::ContinuationControl* node) override + virtual void visit(AST::ContinuationControl const* node) override { NodeVisitor::visit(node); auto& span = span_for_node(node); span.attributes.color = m_palette.syntax_control_keyword(); } - virtual void visit(const AST::DynamicEvaluate* node) override + virtual void visit(AST::DynamicEvaluate const* node) override { NodeVisitor::visit(node); @@ -182,7 +182,7 @@ private: start_span.attributes.color = m_palette.syntax_punctuation(); start_span.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 1 }); } - virtual void visit(const AST::DoubleQuotedString* node) override + virtual void visit(AST::DoubleQuotedString const* node) override { NodeVisitor::visit(node); @@ -202,11 +202,11 @@ private: } m_is_first_in_command = false; } - virtual void visit(const AST::Fd2FdRedirection* node) override + virtual void visit(AST::Fd2FdRedirection const* node) override { NodeVisitor::visit(node); } - virtual void visit(const AST::FunctionDeclaration* node) override + virtual void visit(AST::FunctionDeclaration const* node) override { NodeVisitor::visit(node); @@ -224,7 +224,7 @@ private: name_span.attributes.color = m_palette.syntax_identifier(); } } - virtual void visit(const AST::ForLoop* node) override + virtual void visit(AST::ForLoop const* node) override { // The iterated expression is an expression, not a command. m_is_first_in_command = false; @@ -275,14 +275,14 @@ private: variable_span.attributes.color = m_palette.syntax_identifier(); } } - virtual void visit(const AST::Glob* node) override + virtual void visit(AST::Glob const* node) override { NodeVisitor::visit(node); auto& span = span_for_node(node); span.attributes.color = m_palette.syntax_preprocessor_value(); } - virtual void visit(const AST::Execute* node) override + virtual void visit(AST::Execute const* node) override { TemporaryChange first { m_is_first_in_command, true }; NodeVisitor::visit(node); @@ -299,7 +299,7 @@ private: end_span.data = static_cast(AugmentedTokenKind::CloseParen); } } - virtual void visit(const AST::IfCond* node) override + virtual void visit(AST::IfCond const* node) override { m_is_first_in_command = false; NodeVisitor::visit(node); @@ -321,7 +321,7 @@ private: } } - virtual void visit(const AST::ImmediateExpression* node) override + virtual void visit(AST::ImmediateExpression const* node) override { TemporaryChange first { m_is_first_in_command, false }; NodeVisitor::visit(node); @@ -345,11 +345,11 @@ private: end_span.data = static_cast(AugmentedTokenKind::CloseParen); } - virtual void visit(const AST::Join* node) override + virtual void visit(AST::Join const* node) override { NodeVisitor::visit(node); } - virtual void visit(const AST::MatchExpr* node) override + virtual void visit(AST::MatchExpr const* node) override { // The matched expression is an expression, not a command. m_is_first_in_command = false; @@ -371,7 +371,7 @@ private: as_span.attributes.color = m_palette.syntax_keyword(); } } - virtual void visit(const AST::Or* node) override + virtual void visit(AST::Or const* node) override { { ScopedValueRollback first_in_command { m_is_first_in_command }; @@ -388,11 +388,11 @@ private: span.attributes.color = m_palette.syntax_punctuation(); span.attributes.bold = true; } - virtual void visit(const AST::Pipe* node) override + virtual void visit(AST::Pipe const* node) override { NodeVisitor::visit(node); } - virtual void visit(const AST::Range* node) override + virtual void visit(AST::Range const* node) override { NodeVisitor::visit(node); @@ -409,15 +409,15 @@ private: end_span.attributes.color = m_palette.syntax_punctuation(); } - virtual void visit(const AST::ReadRedirection* node) override + virtual void visit(AST::ReadRedirection const* node) override { NodeVisitor::visit(node); } - virtual void visit(const AST::ReadWriteRedirection* node) override + virtual void visit(AST::ReadWriteRedirection const* node) override { NodeVisitor::visit(node); } - virtual void visit(const AST::Sequence* node) override + virtual void visit(AST::Sequence const* node) override { for (auto& entry : node->entries()) { ScopedValueRollback first_in_command { m_is_first_in_command }; @@ -435,29 +435,29 @@ private: span.is_skippable = true; } } - virtual void visit(const AST::Subshell* node) override + virtual void visit(AST::Subshell const* node) override { NodeVisitor::visit(node); } - virtual void visit(const AST::SimpleVariable* node) override + virtual void visit(AST::SimpleVariable const* node) override { NodeVisitor::visit(node); auto& span = span_for_node(node); span.attributes.color = m_palette.syntax_identifier(); } - virtual void visit(const AST::SpecialVariable* node) override + virtual void visit(AST::SpecialVariable const* node) override { NodeVisitor::visit(node); auto& span = span_for_node(node); span.attributes.color = m_palette.syntax_identifier(); } - virtual void visit(const AST::Juxtaposition* node) override + virtual void visit(AST::Juxtaposition const* node) override { NodeVisitor::visit(node); } - virtual void visit(const AST::StringLiteral* node) override + virtual void visit(AST::StringLiteral const* node) override { NodeVisitor::visit(node); @@ -470,11 +470,11 @@ private: span.attributes.bold = true; m_is_first_in_command = false; } - virtual void visit(const AST::StringPartCompose* node) override + virtual void visit(AST::StringPartCompose const* node) override { NodeVisitor::visit(node); } - virtual void visit(const AST::SyntaxError* node) override + virtual void visit(AST::SyntaxError const* node) override { NodeVisitor::visit(node); @@ -483,14 +483,14 @@ private: span.attributes.background_color = Color(Color::NamedColor::MidRed).lightened(1.3f).with_alpha(128); span.attributes.color = m_palette.base_text(); } - virtual void visit(const AST::Tilde* node) override + virtual void visit(AST::Tilde const* node) override { NodeVisitor::visit(node); auto& span = span_for_node(node); span.attributes.color = m_palette.link(); } - virtual void visit(const AST::VariableDeclarations* node) override + virtual void visit(AST::VariableDeclarations const* node) override { TemporaryChange first_in_command { m_is_first_in_command, false }; for (auto& decl : node->variables()) { @@ -506,18 +506,18 @@ private: start_span.data = static_cast(AugmentedTokenKind::OpenParen); } } - virtual void visit(const AST::WriteAppendRedirection* node) override + virtual void visit(AST::WriteAppendRedirection const* node) override { NodeVisitor::visit(node); } - virtual void visit(const AST::WriteRedirection* node) override + virtual void visit(AST::WriteRedirection const* node) override { NodeVisitor::visit(node); } - Vector& m_spans; + Vector& m_spans; Gfx::Palette const& m_palette; - const GUI::TextDocument& m_document; + Syntax::Document const& m_document; bool m_is_first_in_command { false }; }; @@ -544,7 +544,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) Parser parser(text); auto ast = parser.parse(); - Vector spans; + Vector spans; HighlightVisitor visitor { spans, palette, m_client->get_document() }; if (ast)