mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-26 19:32:06 -05:00
LibGUI: Add folding regions to TextDocument
A `TextDocumentFoldingRegion` represents a region of the document which the user can collapse/expand to make code easier to navigate.
This commit is contained in:
parent
96cf9355b2
commit
3d25b4eb34
2 changed files with 100 additions and 0 deletions
|
@ -47,6 +47,7 @@ bool TextDocument::set_text(StringView text, AllowCallback allow_callback)
|
||||||
m_client_notifications_enabled = false;
|
m_client_notifications_enabled = false;
|
||||||
m_undo_stack.clear();
|
m_undo_stack.clear();
|
||||||
m_spans.clear();
|
m_spans.clear();
|
||||||
|
m_folding_regions.clear();
|
||||||
remove_all_lines();
|
remove_all_lines();
|
||||||
|
|
||||||
ArmedScopeGuard clear_text_guard([this]() {
|
ArmedScopeGuard clear_text_guard([this]() {
|
||||||
|
@ -1436,4 +1437,84 @@ void TextDocument::merge_span_collections()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextDocument::set_folding_regions(Vector<TextDocumentFoldingRegion> 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<TextDocumentFoldingRegion&> 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<TextDocumentFoldingRegion const&> TextDocument::currently_folded_regions() const
|
||||||
|
{
|
||||||
|
Vector<TextDocumentFoldingRegion const&> 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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||||
* Copyright (c) 2022, the SerenityOS developers.
|
* Copyright (c) 2022, the SerenityOS developers.
|
||||||
|
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -35,6 +36,13 @@ struct TextDocumentSpan {
|
||||||
bool is_skippable { false };
|
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<class TextDocumentLine> line_ptr;
|
||||||
|
};
|
||||||
|
|
||||||
class TextDocument : public RefCounted<TextDocument> {
|
class TextDocument : public RefCounted<TextDocument> {
|
||||||
public:
|
public:
|
||||||
enum class SearchShouldWrap {
|
enum class SearchShouldWrap {
|
||||||
|
@ -79,6 +87,16 @@ public:
|
||||||
|
|
||||||
TextDocumentSpan const* span_at(TextPosition const&) const;
|
TextDocumentSpan const* span_at(TextPosition const&) const;
|
||||||
|
|
||||||
|
void set_folding_regions(Vector<TextDocumentFoldingRegion>);
|
||||||
|
bool has_folding_regions() const { return !m_folding_regions.is_empty(); }
|
||||||
|
Vector<TextDocumentFoldingRegion>& folding_regions() { return m_folding_regions; }
|
||||||
|
Vector<TextDocumentFoldingRegion> const& folding_regions() const { return m_folding_regions; }
|
||||||
|
Optional<TextDocumentFoldingRegion&> folding_region_starting_on_line(size_t line);
|
||||||
|
// Returns all folded FoldingRegions that are not contained inside another folded region.
|
||||||
|
Vector<TextDocumentFoldingRegion const&> 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<TextDocumentLine>);
|
void append_line(NonnullOwnPtr<TextDocumentLine>);
|
||||||
NonnullOwnPtr<TextDocumentLine> take_line(size_t line_index);
|
NonnullOwnPtr<TextDocumentLine> take_line(size_t line_index);
|
||||||
void remove_line(size_t line_index);
|
void remove_line(size_t line_index);
|
||||||
|
@ -148,6 +166,7 @@ private:
|
||||||
NonnullOwnPtrVector<TextDocumentLine> m_lines;
|
NonnullOwnPtrVector<TextDocumentLine> m_lines;
|
||||||
HashMap<u32, Vector<TextDocumentSpan>> m_span_collections;
|
HashMap<u32, Vector<TextDocumentSpan>> m_span_collections;
|
||||||
Vector<TextDocumentSpan> m_spans;
|
Vector<TextDocumentSpan> m_spans;
|
||||||
|
Vector<TextDocumentFoldingRegion> m_folding_regions;
|
||||||
|
|
||||||
HashTable<Client*> m_clients;
|
HashTable<Client*> m_clients;
|
||||||
bool m_client_notifications_enabled { true };
|
bool m_client_notifications_enabled { true };
|
||||||
|
|
Loading…
Add table
Reference in a new issue