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)