From 10700ca4c1cabe7889b0031bc01a3b18c68df77d Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Sun, 16 Apr 2023 10:28:45 +0100 Subject: [PATCH] LibManual: Associate SubsectionNode with similarly named markdown file For a subsection named `Foo` with a markdown file `Foo.md` in the same directory, the document `Foo.md` will be returned when the subsection is selected. This prevents PageNodes and SubsectionNodes from sharing the same name, which fixes an issue in Help where subsections could not be navigated. --- Userland/Libraries/LibManual/SectionNode.cpp | 39 ++++++++++++------- .../Libraries/LibManual/SubsectionNode.cpp | 21 ++-------- Userland/Libraries/LibManual/SubsectionNode.h | 6 ++- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/Userland/Libraries/LibManual/SectionNode.cpp b/Userland/Libraries/LibManual/SectionNode.cpp index 150f960f32..cc2a39f07a 100644 --- a/Userland/Libraries/LibManual/SectionNode.cpp +++ b/Userland/Libraries/LibManual/SectionNode.cpp @@ -8,6 +8,7 @@ #include "PageNode.h" #include "Path.h" #include "SubsectionNode.h" +#include #include #include #include @@ -43,7 +44,16 @@ ErrorOr SectionNode::reify_if_needed() const m_reified = true; auto own_path = TRY(path()); - Core::DirIterator dir_iter { own_path.to_deprecated_string(), Core::DirIterator::Flags::SkipDots }; + Core::DirIterator dir_iterator { own_path.to_deprecated_string(), Core::DirIterator::Flags::SkipDots }; + Vector directories; + HashTable files; + while (dir_iterator.has_next()) { + auto entry = dir_iterator.next(); + if (entry->type == Core::DirectoryEntry::Type::Directory) + TRY(directories.try_append(entry->name)); + else if (entry->type == Core::DirectoryEntry::Type::File && entry->name.ends_with(".md"sv, CaseSensitivity::CaseInsensitive)) + TRY(files.try_set(entry->name)); + } struct Child { NonnullRefPtr node; @@ -51,18 +61,21 @@ ErrorOr SectionNode::reify_if_needed() const }; Vector children; - while (dir_iter.has_next()) { - LexicalPath lexical_path(dir_iter.next_path()); - if (lexical_path.extension() != "md") { - if (FileSystem::is_directory(LexicalPath::absolute_path(own_path.to_deprecated_string(), lexical_path.string()))) { - dbgln("Found subsection {}", lexical_path); - children.append({ .node = TRY(try_make_ref_counted(*this, lexical_path.title())), - .name_for_sorting = TRY(String::from_utf8(lexical_path.title())) }); - } - } else { - children.append({ .node = TRY(try_make_ref_counted(*this, TRY(String::from_utf8(lexical_path.title())))), - .name_for_sorting = TRY(String::from_utf8(lexical_path.title())) }); - } + for (auto const& directory : directories) { + LexicalPath lexical_path(directory); + RefPtr associated_page; + auto matching_page_name = DeprecatedString::formatted("{}.md", directory); + if (files.remove(matching_page_name)) + associated_page = TRY(try_make_ref_counted(*this, TRY(String::from_utf8(lexical_path.title())))); + + TRY(children.try_append({ .node = TRY(try_make_ref_counted(*this, lexical_path.title(), associated_page)), + .name_for_sorting = TRY(String::from_utf8(lexical_path.title())) })); + } + + for (auto const& file : files) { + LexicalPath lexical_path(file); + children.append({ .node = TRY(try_make_ref_counted(*this, TRY(String::from_utf8(lexical_path.title())))), + .name_for_sorting = TRY(String::from_utf8(lexical_path.title())) }); } quick_sort(children, [](auto const& a, auto const& b) { return a.name_for_sorting < b.name_for_sorting; }); diff --git a/Userland/Libraries/LibManual/SubsectionNode.cpp b/Userland/Libraries/LibManual/SubsectionNode.cpp index cb1e6d44fb..f5535a1bf8 100644 --- a/Userland/Libraries/LibManual/SubsectionNode.cpp +++ b/Userland/Libraries/LibManual/SubsectionNode.cpp @@ -10,31 +10,16 @@ namespace Manual { -SubsectionNode::SubsectionNode(NonnullRefPtr parent, StringView name) +SubsectionNode::SubsectionNode(NonnullRefPtr parent, StringView name, RefPtr page) : SectionNode(name, name) , m_parent(move(parent)) + , m_page(move(page)) { } Node const* SubsectionNode::parent() const { return m_parent; } -PageNode const* SubsectionNode::document() const -{ - auto maybe_siblings = parent()->children(); - if (maybe_siblings.is_error()) - return nullptr; - auto siblings = maybe_siblings.release_value(); - for (auto const& sibling : siblings) { - if (&*sibling == this) - continue; - auto sibling_name = sibling->name(); - if (sibling_name.is_error()) - continue; - if (sibling_name.value() == m_name && is(*sibling)) - return static_cast(&*sibling); - } - return nullptr; -} +PageNode const* SubsectionNode::document() const { return m_page; } ErrorOr SubsectionNode::name() const { return m_name; } diff --git a/Userland/Libraries/LibManual/SubsectionNode.h b/Userland/Libraries/LibManual/SubsectionNode.h index 93a4334f97..4ab6fda183 100644 --- a/Userland/Libraries/LibManual/SubsectionNode.h +++ b/Userland/Libraries/LibManual/SubsectionNode.h @@ -6,6 +6,7 @@ #pragma once +#include #include namespace Manual { @@ -13,7 +14,7 @@ namespace Manual { // A non-toplevel (i.e. not numbered) manual section. class SubsectionNode : public SectionNode { public: - SubsectionNode(NonnullRefPtr parent, StringView name); + SubsectionNode(NonnullRefPtr parent, StringView name, RefPtr page = {}); virtual ~SubsectionNode() = default; virtual Node const* parent() const override; @@ -23,6 +24,9 @@ public: protected: NonnullRefPtr m_parent; + +private: + RefPtr m_page; }; }