From ab0b4f46f7a440d16ff5e58d3f63e38daa9c59fb Mon Sep 17 00:00:00 2001 From: Itamar Date: Tue, 29 Mar 2022 16:31:26 +0300 Subject: [PATCH] LibGUI: Support multiple layers of TextDocument spans TextDocument::set_spans() now also takes a "span collection index" argument. TextDocument keeps a map between a span collection index and its spans. It merges the spans from all collections into a single set of spans whenever set_spans() is called. This allows us to style a document with multiple layers of spans, where as previously we only supported a single layer of spans that was set from the SyntaxHighlighter. --- Userland/Libraries/LibGUI/TextDocument.cpp | 98 +++++++++++++++++++ Userland/Libraries/LibGUI/TextDocument.h | 5 +- Userland/Libraries/LibGUI/TextEditor.cpp | 2 +- Userland/Libraries/LibGUI/TextEditor.h | 2 + .../Libraries/LibSyntax/HighlighterClient.h | 2 + 5 files changed, 107 insertions(+), 2 deletions(-) diff --git a/Userland/Libraries/LibGUI/TextDocument.cpp b/Userland/Libraries/LibGUI/TextDocument.cpp index 4cf24c83b6..9d595bf4de 100644 --- a/Userland/Libraries/LibGUI/TextDocument.cpp +++ b/Userland/Libraries/LibGUI/TextDocument.cpp @@ -7,7 +7,9 @@ #include #include +#include #include +#include #include #include #include @@ -982,4 +984,100 @@ 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 = span.attributes.underline | last_span.attributes.underline; + 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; + 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(); + for (auto span : merged_spans) { + m_spans.append(move(span.span)); + } +} + } diff --git a/Userland/Libraries/LibGUI/TextDocument.h b/Userland/Libraries/LibGUI/TextDocument.h index db611dda69..ee43a0cf2b 100644 --- a/Userland/Libraries/LibGUI/TextDocument.h +++ b/Userland/Libraries/LibGUI/TextDocument.h @@ -62,7 +62,7 @@ public: const TextDocumentLine& 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(Vector spans) { m_spans = move(spans); } + void set_spans(u32 span_collection_index, Vector spans); bool set_text(StringView, AllowCallback = AllowCallback::Yes); @@ -136,7 +136,10 @@ protected: explicit TextDocument(Client* client); private: + void merge_span_collections(); + NonnullOwnPtrVector m_lines; + HashMap> m_span_collections; Vector m_spans; HashTable m_clients; diff --git a/Userland/Libraries/LibGUI/TextEditor.cpp b/Userland/Libraries/LibGUI/TextEditor.cpp index 578dbe033d..36cbca5f88 100644 --- a/Userland/Libraries/LibGUI/TextEditor.cpp +++ b/Userland/Libraries/LibGUI/TextEditor.cpp @@ -1951,7 +1951,7 @@ void TextEditor::set_syntax_highlighter(OwnPtr highlighter) m_highlighter->attach(*this); m_needs_rehighlight = true; } else - document().set_spans({}); + document().set_spans(Syntax::HighlighterClient::span_collection_index, {}); if (on_highlighter_change) on_highlighter_change(); } diff --git a/Userland/Libraries/LibGUI/TextEditor.h b/Userland/Libraries/LibGUI/TextEditor.h index 98e1ae56bb..e3d2fa454e 100644 --- a/Userland/Libraries/LibGUI/TextEditor.h +++ b/Userland/Libraries/LibGUI/TextEditor.h @@ -242,6 +242,8 @@ protected: int ruler_width() const; int gutter_width() const; + virtual void highlighter_did_set_spans(Vector spans) final { document().set_spans(Syntax::HighlighterClient::span_collection_index, move(spans)); } + private: friend class TextDocumentLine; diff --git a/Userland/Libraries/LibSyntax/HighlighterClient.h b/Userland/Libraries/LibSyntax/HighlighterClient.h index d652375246..60ee1bb9ab 100644 --- a/Userland/Libraries/LibSyntax/HighlighterClient.h +++ b/Userland/Libraries/LibSyntax/HighlighterClient.h @@ -33,6 +33,8 @@ public: String 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(); } + + static constexpr auto span_collection_index = 0; }; }