summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
Diffstat (limited to 'Userland')
-rw-r--r--Userland/Applications/Help/CMakeLists.txt5
-rw-r--r--Userland/Applications/Help/MainWidget.cpp80
-rw-r--r--Userland/Applications/Help/MainWidget.h4
-rw-r--r--Userland/Applications/Help/ManualModel.cpp99
-rw-r--r--Userland/Applications/Help/ManualModel.h12
-rw-r--r--Userland/Applications/Help/ManualNode.h22
-rw-r--r--Userland/Applications/Help/ManualPageNode.cpp25
-rw-r--r--Userland/Applications/Help/ManualPageNode.h33
-rw-r--r--Userland/Applications/Help/ManualSectionNode.cpp46
-rw-r--r--Userland/Applications/Help/ManualSectionNode.h44
-rw-r--r--Userland/Applications/Help/main.cpp2
-rw-r--r--Userland/Libraries/CMakeLists.txt1
-rw-r--r--Userland/Libraries/LibManual/CMakeLists.txt7
-rw-r--r--Userland/Libraries/LibManual/Node.h30
-rw-r--r--Userland/Libraries/LibManual/PageNode.cpp29
-rw-r--r--Userland/Libraries/LibManual/PageNode.h38
-rw-r--r--Userland/Libraries/LibManual/SectionNode.cpp62
-rw-r--r--Userland/Libraries/LibManual/SectionNode.h65
18 files changed, 339 insertions, 265 deletions
diff --git a/Userland/Applications/Help/CMakeLists.txt b/Userland/Applications/Help/CMakeLists.txt
index 9f7f9ee67a..7b037da522 100644
--- a/Userland/Applications/Help/CMakeLists.txt
+++ b/Userland/Applications/Help/CMakeLists.txt
@@ -12,8 +12,6 @@ set(SOURCES
main.cpp
MainWidget.cpp
ManualModel.cpp
- ManualPageNode.cpp
- ManualSectionNode.cpp
)
set(GENERATED_SOURCES
@@ -21,4 +19,5 @@ set(GENERATED_SOURCES
)
serenity_app(Help ICON app-help)
-target_link_libraries(Help PRIVATE LibCore LibWebView LibWeb LibMarkdown LibGfx LibGUI LibDesktop LibMain)
+target_link_libraries(Help PRIVATE LibCore LibWebView LibWeb LibMarkdown LibGfx LibGUI LibDesktop LibMain LibManual)
+link_with_locale_data(Help)
diff --git a/Userland/Applications/Help/MainWidget.cpp b/Userland/Applications/Help/MainWidget.cpp
index 796ff97265..ffe7b88a98 100644
--- a/Userland/Applications/Help/MainWidget.cpp
+++ b/Userland/Applications/Help/MainWidget.cpp
@@ -8,7 +8,9 @@
*/
#include "MainWidget.h"
-#include <AK/DeprecatedString.h>
+#include <AK/LexicalPath.h>
+#include <AK/String.h>
+#include <AK/StringView.h>
#include <AK/URL.h>
#include <Applications/Help/HelpWindowGML.h>
#include <LibCore/ArgsParser.h>
@@ -30,6 +32,9 @@
#include <LibGUI/Window.h>
#include <LibGfx/Bitmap.h>
#include <LibMain/Main.h>
+#include <LibManual/Node.h>
+#include <LibManual/PageNode.h>
+#include <LibManual/SectionNode.h>
#include <LibMarkdown/Document.h>
namespace Help {
@@ -66,25 +71,25 @@ MainWidget::MainWidget()
}
auto& search_model = *static_cast<GUI::FilteringProxyModel*>(view_model);
auto const& mapped_index = search_model.map(index);
- DeprecatedString path = m_manual_model->page_path(mapped_index);
- if (path.is_null()) {
+ auto path = m_manual_model->page_path(mapped_index);
+ if (!path.has_value()) {
m_web_view->load_empty_document();
return;
}
m_browse_view->selection().clear();
m_browse_view->selection().add(mapped_index);
- m_history.push(path);
- open_page(path);
+ m_history.push(path.value());
+ open_page(path.value());
};
m_browse_view = find_descendant_of_type_named<GUI::TreeView>("browse_view");
m_browse_view->on_selection_change = [this] {
- DeprecatedString path = m_manual_model->page_path(m_browse_view->selection().first());
- if (path.is_null())
+ auto path = m_manual_model->page_path(m_browse_view->selection().first());
+ if (!path.has_value())
return;
- m_history.push(path);
- open_page(path);
+ m_history.push(path.value());
+ open_page(path.value());
};
m_browse_view->on_toggle = [this](GUI::ModelIndex const& index, bool open) {
m_manual_model->update_section_node_on_toggle(index, open);
@@ -105,7 +110,7 @@ MainWidget::MainWidget()
return;
}
m_history.push(path);
- open_page(path);
+ open_page(MUST(String::from_utf8(path)));
} else if (url.scheme() == "help") {
if (url.host() == "man") {
if (url.paths().size() != 2) {
@@ -138,23 +143,17 @@ MainWidget::MainWidget()
m_go_back_action = GUI::CommonActions::make_go_back_action([this](auto&) {
m_history.go_back();
- open_page(m_history.current());
+ open_page(MUST(String::from_deprecated_string(m_history.current())));
});
m_go_forward_action = GUI::CommonActions::make_go_forward_action([this](auto&) {
m_history.go_forward();
- open_page(m_history.current());
+ open_page(MUST(String::from_deprecated_string(m_history.current())));
});
m_go_back_action->set_enabled(false);
m_go_forward_action->set_enabled(false);
- m_go_home_action = GUI::CommonActions::make_go_home_action([this](auto&) {
- DeprecatedString path = "/usr/share/man/man7/Help-index.md";
- m_history.push(path);
- open_page(path);
- });
-
m_copy_action = GUI::CommonActions::make_copy_action([this](auto&) {
auto selected_text = m_web_view->selected_text();
if (!selected_text.is_empty())
@@ -174,37 +173,27 @@ MainWidget::MainWidget()
};
}
-void MainWidget::set_start_page(StringView start_page, u32 section)
+ErrorOr<void> MainWidget::set_start_page(StringView start_page, u32 section)
{
bool set_start_page = false;
if (!start_page.is_null()) {
if (section != 0) {
// > Help [section] [name]
- DeprecatedString path = DeprecatedString::formatted("/usr/share/man/man{}/{}.md", section, start_page);
+ String path = TRY(String::formatted("/usr/share/man/man{}/{}.md", section, start_page));
m_history.push(path);
open_page(path);
set_start_page = true;
} else if (URL url = URL::create_with_url_or_path(start_page); url.is_valid() && url.path().ends_with(".md"sv)) {
// > Help [/path/to/documentation/file.md]
m_history.push(url.path());
- open_page(url.path());
+ open_page(TRY(String::from_deprecated_string(url.path())));
set_start_page = true;
} else {
// > Help [query]
// First, see if we can find the page by name
- char const* sections[] = {
- "1",
- "2",
- "3",
- "4",
- "5",
- "6",
- "7",
- "8"
- };
- for (auto s : sections) {
- DeprecatedString path = DeprecatedString::formatted("/usr/share/man/man{}/{}.md", s, start_page);
+ for (auto s : Manual::section_numbers) {
+ String path = TRY(String::formatted("/usr/share/man/man{}/{}.md", s, start_page));
if (Core::File::exists(path)) {
m_history.push(path);
open_page(path);
@@ -225,10 +214,17 @@ void MainWidget::set_start_page(StringView start_page, u32 section)
}
if (!set_start_page)
m_go_home_action->activate();
+ return {};
}
ErrorOr<void> MainWidget::initialize_fallibles(GUI::Window& window)
{
+ static String help_index_path = TRY(String::from_utf8("/usr/share/man/man7/Help-index.md"sv));
+ m_go_home_action = GUI::CommonActions::make_go_home_action([this](auto&) {
+ m_history.push(help_index_path);
+ open_page(help_index_path);
+ });
+
(void)TRY(m_toolbar->try_add_action(*m_go_back_action));
(void)TRY(m_toolbar->try_add_action(*m_go_forward_action));
(void)TRY(m_toolbar->try_add_action(*m_go_home_action));
@@ -244,10 +240,10 @@ ErrorOr<void> MainWidget::initialize_fallibles(GUI::Window& window)
TRY(go_menu->try_add_action(*m_go_home_action));
auto help_menu = TRY(window.try_add_menu("&Help"));
+ static String help_page_path = TRY(String::from_utf8("/usr/share/man/man1/Help.md"sv));
TRY(help_menu->try_add_action(GUI::CommonActions::make_command_palette_action(&window)));
TRY(help_menu->try_add_action(GUI::Action::create("&Contents", { Key_F1 }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/filetype-unknown.png"sv)), [&](auto&) {
- DeprecatedString path = "/usr/share/man/man1/Help.md";
- open_page(path);
+ open_page(help_page_path);
})));
TRY(help_menu->try_add_action(GUI::CommonActions::make_about_action("Help", TRY(GUI::Icon::try_create_default_icon("app-help"sv)), &window)));
@@ -282,8 +278,12 @@ void MainWidget::open_url(URL const& url)
if (browse_view_index.has_value()) {
m_browse_view->expand_tree(browse_view_index.value().parent());
- DeprecatedString page_and_section = m_manual_model->page_and_section(browse_view_index.value());
- window()->set_title(DeprecatedString::formatted("{} - Help", page_and_section));
+ auto page_and_section = m_manual_model->page_and_section(browse_view_index.value());
+ if (!page_and_section.has_value())
+ return;
+ auto title = String::formatted("{} - Help", page_and_section.value());
+ if (!title.is_error())
+ window()->set_title(title.release_value().to_deprecated_string());
} else {
window()->set_title("Help");
}
@@ -297,17 +297,17 @@ void MainWidget::open_external(URL const& url)
GUI::MessageBox::show(window(), DeprecatedString::formatted("The link to '{}' could not be opened.", url), "Failed to open link"sv, GUI::MessageBox::Type::Error);
}
-void MainWidget::open_page(DeprecatedString const& path)
+void MainWidget::open_page(Optional<String> const& path)
{
m_go_back_action->set_enabled(m_history.can_go_back());
m_go_forward_action->set_enabled(m_history.can_go_forward());
- if (path.is_null()) {
+ if (!path.has_value()) {
window()->set_title("Help");
m_web_view->load_empty_document();
return;
}
- open_url(URL::create_with_url_or_path(path));
+ open_url(URL::create_with_url_or_path(path.value().to_deprecated_string()));
}
}
diff --git a/Userland/Applications/Help/MainWidget.h b/Userland/Applications/Help/MainWidget.h
index 18f3aa91be..8f1012a84b 100644
--- a/Userland/Applications/Help/MainWidget.h
+++ b/Userland/Applications/Help/MainWidget.h
@@ -20,13 +20,13 @@ public:
virtual ~MainWidget() override = default;
ErrorOr<void> initialize_fallibles(GUI::Window&);
- void set_start_page(StringView page, u32 section);
+ ErrorOr<void> set_start_page(StringView page, u32 section);
private:
MainWidget();
void open_url(URL const&);
- void open_page(DeprecatedString const& path);
+ void open_page(Optional<String> const& path);
void open_external(URL const&);
History m_history;
diff --git a/Userland/Applications/Help/ManualModel.cpp b/Userland/Applications/Help/ManualModel.cpp
index cf07fedc1b..1584d349a3 100644
--- a/Userland/Applications/Help/ManualModel.cpp
+++ b/Userland/Applications/Help/ManualModel.cpp
@@ -5,21 +5,11 @@
*/
#include "ManualModel.h"
-#include "ManualNode.h"
-#include "ManualPageNode.h"
-#include "ManualSectionNode.h"
#include <AK/Try.h>
-
-static ManualSectionNode s_sections[] = {
- { "1", "User Programs" },
- { "2", "System Calls" },
- { "3", "Library Functions" },
- { "4", "Special Files" },
- { "5", "File Formats" },
- { "6", "Games" },
- { "7", "Miscellanea" },
- { "8", "Sysadmin Tools" }
-};
+#include <AK/Utf8View.h>
+#include <LibManual/Node.h>
+#include <LibManual/PageNode.h>
+#include <LibManual/SectionNode.h>
ManualModel::ManualModel()
{
@@ -34,11 +24,14 @@ Optional<GUI::ModelIndex> ManualModel::index_from_path(StringView path) const
auto parent_index = index(section, 0);
for (int row = 0; row < row_count(parent_index); ++row) {
auto child_index = index(row, 0, parent_index);
- auto* node = static_cast<ManualNode const*>(child_index.internal_data());
+ auto* node = static_cast<Manual::Node const*>(child_index.internal_data());
if (!node->is_page())
continue;
- auto* page = static_cast<ManualPageNode const*>(node);
- if (page->path() != path)
+ auto* page = static_cast<Manual::PageNode const*>(node);
+ auto const maybe_path = page->path();
+ if (maybe_path.is_error())
+ return {};
+ if (maybe_path.value().bytes_as_string_view() != path)
continue;
return child_index;
}
@@ -46,29 +39,32 @@ Optional<GUI::ModelIndex> ManualModel::index_from_path(StringView path) const
return {};
}
-DeprecatedString ManualModel::page_name(const GUI::ModelIndex& index) const
+Optional<String> ManualModel::page_name(const GUI::ModelIndex& index) const
{
if (!index.is_valid())
return {};
- auto* node = static_cast<ManualNode const*>(index.internal_data());
+ auto* node = static_cast<Manual::Node const*>(index.internal_data());
if (!node->is_page())
return {};
- auto* page = static_cast<ManualPageNode const*>(node);
+ auto* page = static_cast<Manual::PageNode const*>(node);
return page->name();
}
-DeprecatedString ManualModel::page_path(const GUI::ModelIndex& index) const
+Optional<String> ManualModel::page_path(const GUI::ModelIndex& index) const
{
if (!index.is_valid())
return {};
- auto* node = static_cast<ManualNode const*>(index.internal_data());
+ auto* node = static_cast<Manual::Node const*>(index.internal_data());
if (!node->is_page())
return {};
- auto* page = static_cast<ManualPageNode const*>(node);
- return page->path();
+ auto* page = static_cast<Manual::PageNode const*>(node);
+ auto path = page->path();
+ if (path.is_error())
+ return {};
+ return path.release_value();
}
-ErrorOr<StringView> ManualModel::page_view(DeprecatedString const& path) const
+ErrorOr<StringView> ManualModel::page_view(String const& path) const
{
if (path.is_empty())
return StringView {};
@@ -87,23 +83,29 @@ ErrorOr<StringView> ManualModel::page_view(DeprecatedString const& path) const
return view;
}
-DeprecatedString ManualModel::page_and_section(const GUI::ModelIndex& index) const
+Optional<String> ManualModel::page_and_section(const GUI::ModelIndex& index) const
{
if (!index.is_valid())
return {};
- auto* node = static_cast<ManualNode const*>(index.internal_data());
+ auto* node = static_cast<Manual::Node const*>(index.internal_data());
if (!node->is_page())
return {};
- auto* page = static_cast<ManualPageNode const*>(node);
- auto* section = static_cast<ManualSectionNode const*>(page->parent());
- return DeprecatedString::formatted("{}({})", page->name(), section->section_name());
+ auto* page = static_cast<Manual::PageNode const*>(node);
+ auto* section = static_cast<Manual::SectionNode const*>(page->parent());
+ auto page_name = page->name();
+ if (page_name.is_error())
+ return {};
+ auto name = String::formatted("{}({})", page_name.release_value(), section->section_name());
+ if (name.is_error())
+ return {};
+ return name.release_value();
}
GUI::ModelIndex ManualModel::index(int row, int column, const GUI::ModelIndex& parent_index) const
{
if (!parent_index.is_valid())
- return create_index(row, column, &s_sections[row]);
- auto* parent = static_cast<ManualNode const*>(parent_index.internal_data());
+ return create_index(row, column, Manual::sections[row].ptr());
+ auto* parent = static_cast<Manual::Node const*>(parent_index.internal_data());
auto* child = &parent->children()[row];
return create_index(row, column, child);
}
@@ -112,19 +114,19 @@ GUI::ModelIndex ManualModel::parent_index(const GUI::ModelIndex& index) const
{
if (!index.is_valid())
return {};
- auto* child = static_cast<ManualNode const*>(index.internal_data());
+ auto* child = static_cast<Manual::Node const*>(index.internal_data());
auto* parent = child->parent();
if (parent == nullptr)
return {};
if (parent->parent() == nullptr) {
- for (size_t row = 0; row < sizeof(s_sections) / sizeof(s_sections[0]); row++)
- if (&s_sections[row] == parent)
+ for (size_t row = 0; row < sizeof(Manual::sections) / sizeof(Manual::sections[0]); row++)
+ if (Manual::sections[row].ptr() == parent)
return create_index(row, 0, parent);
VERIFY_NOT_REACHED();
}
for (size_t row = 0; row < parent->parent()->children().size(); row++) {
- ManualNode* child_at_row = &parent->parent()->children()[row];
+ Manual::Node* child_at_row = &parent->parent()->children()[row];
if (child_at_row == parent)
return create_index(row, 0, parent);
}
@@ -134,8 +136,8 @@ GUI::ModelIndex ManualModel::parent_index(const GUI::ModelIndex& index) const
int ManualModel::row_count(const GUI::ModelIndex& index) const
{
if (!index.is_valid())
- return sizeof(s_sections) / sizeof(s_sections[0]);
- auto* node = static_cast<ManualNode const*>(index.internal_data());
+ return sizeof(Manual::sections) / sizeof(Manual::sections[0]);
+ auto* node = static_cast<Manual::Node const*>(index.internal_data());
return node->children().size();
}
@@ -146,12 +148,16 @@ int ManualModel::column_count(const GUI::ModelIndex&) const
GUI::Variant ManualModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
{
- auto* node = static_cast<ManualNode const*>(index.internal_data());
+ auto* node = static_cast<Manual::Node const*>(index.internal_data());
switch (role) {
case GUI::ModelRole::Search:
if (!node->is_page())
return {};
- return DeprecatedString(page_view(page_path(index)).value());
+ if (auto path = page_path(index); path.has_value())
+ if (auto page = page_view(path.release_value()); !page.is_error())
+ // FIXME: We already provide String, but GUI::Variant still needs DeprecatedString.
+ return DeprecatedString(page.release_value());
+ return {};
case GUI::ModelRole::Display:
return node->name();
case GUI::ModelRole::Icon:
@@ -167,17 +173,24 @@ GUI::Variant ManualModel::data(const GUI::ModelIndex& index, GUI::ModelRole role
void ManualModel::update_section_node_on_toggle(const GUI::ModelIndex& index, bool const open)
{
- auto* node = static_cast<ManualSectionNode*>(index.internal_data());
+ auto* node = static_cast<Manual::SectionNode*>(index.internal_data());
node->set_open(open);
}
TriState ManualModel::data_matches(const GUI::ModelIndex& index, const GUI::Variant& term) const
{
auto name = page_name(index);
- if (name.contains(term.as_string(), CaseSensitivity::CaseInsensitive))
+ if (!name.has_value())
+ return TriState::False;
+
+ if (name.value().bytes_as_string_view().contains(term.as_string(), CaseSensitivity::CaseInsensitive))
return TriState::True;
- auto view_result = page_view(page_path(index));
+ auto path = page_path(index);
+ // NOTE: This is slightly inaccurate, as page_path can also fail due to OOM. We consider it acceptable to have a data mismatch in that case.
+ if (!path.has_value())
+ return TriState::False;
+ auto view_result = page_view(path.release_value());
if (view_result.is_error() || view_result.value().is_empty())
return TriState::False;
diff --git a/Userland/Applications/Help/ManualModel.h b/Userland/Applications/Help/ManualModel.h
index 8655fe1009..41415d9605 100644
--- a/Userland/Applications/Help/ManualModel.h
+++ b/Userland/Applications/Help/ManualModel.h
@@ -6,10 +6,10 @@
#pragma once
-#include <AK/DeprecatedString.h>
#include <AK/NonnullRefPtr.h>
#include <AK/Optional.h>
#include <AK/Result.h>
+#include <AK/String.h>
#include <LibGUI/Model.h>
class ManualModel final : public GUI::Model {
@@ -23,10 +23,10 @@ public:
Optional<GUI::ModelIndex> index_from_path(StringView) const;
- DeprecatedString page_name(const GUI::ModelIndex&) const;
- DeprecatedString page_path(const GUI::ModelIndex&) const;
- DeprecatedString page_and_section(const GUI::ModelIndex&) const;
- ErrorOr<StringView> page_view(DeprecatedString const& path) const;
+ Optional<String> page_name(const GUI::ModelIndex&) const;
+ Optional<String> page_path(const GUI::ModelIndex&) const;
+ Optional<String> page_and_section(const GUI::ModelIndex&) const;
+ ErrorOr<StringView> page_view(String const& path) const;
void update_section_node_on_toggle(const GUI::ModelIndex&, bool const);
virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
@@ -42,5 +42,5 @@ private:
GUI::Icon m_section_open_icon;
GUI::Icon m_section_icon;
GUI::Icon m_page_icon;
- mutable HashMap<DeprecatedString, NonnullRefPtr<Core::MappedFile>> m_mapped_files;
+ mutable HashMap<String, NonnullRefPtr<Core::MappedFile>> m_mapped_files;
};
diff --git a/Userland/Applications/Help/ManualNode.h b/Userland/Applications/Help/ManualNode.h
deleted file mode 100644
index d9157b06fc..0000000000
--- a/Userland/Applications/Help/ManualNode.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
- * Copyright (c) 2022, the SerenityOS developers.
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#pragma once
-
-#include <AK/DeprecatedString.h>
-#include <AK/NonnullOwnPtrVector.h>
-
-class ManualNode {
-public:
- virtual ~ManualNode() = default;
-
- virtual NonnullOwnPtrVector<ManualNode>& children() const = 0;
- virtual ManualNode const* parent() const = 0;
- virtual DeprecatedString name() const = 0;
- virtual bool is_page() const { return false; }
- virtual bool is_open() const { return false; }
-};
diff --git a/Userland/Applications/Help/ManualPageNode.cpp b/Userland/Applications/Help/ManualPageNode.cpp
deleted file mode 100644
index 52223c5d46..0000000000
--- a/Userland/Applications/Help/ManualPageNode.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
- * Copyright (c) 2022, the SerenityOS developers.
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#include "ManualPageNode.h"
-#include "ManualSectionNode.h"
-
-ManualNode const* ManualPageNode::parent() const
-{
- return &m_section;
-}
-
-NonnullOwnPtrVector<ManualNode>& ManualPageNode::children() const
-{
- static NonnullOwnPtrVector<ManualNode> empty_vector;
- return empty_vector;
-}
-
-DeprecatedString ManualPageNode::path() const
-{
- return DeprecatedString::formatted("{}/{}.md", m_section.path(), m_page);
-}
diff --git a/Userland/Applications/Help/ManualPageNode.h b/Userland/Applications/Help/ManualPageNode.h
deleted file mode 100644
index e0a9ade594..0000000000
--- a/Userland/Applications/Help/ManualPageNode.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#pragma once
-
-#include "ManualNode.h"
-
-class ManualSectionNode;
-
-class ManualPageNode : public ManualNode {
-public:
- virtual ~ManualPageNode() override = default;
-
- ManualPageNode(ManualSectionNode const& section, StringView page)
- : m_section(section)
- , m_page(page)
- {
- }
-
- virtual NonnullOwnPtrVector<ManualNode>& children() const override;
- virtual ManualNode const* parent() const override;
- virtual DeprecatedString name() const override { return m_page; };
- virtual bool is_page() const override { return true; }
-
- DeprecatedString path() const;
-
-private:
- ManualSectionNode const& m_section;
- DeprecatedString m_page;
-};
diff --git a/Userland/Applications/Help/ManualSectionNode.cpp b/Userland/Applications/Help/ManualSectionNode.cpp
deleted file mode 100644
index 2259715693..0000000000
--- a/Userland/Applications/Help/ManualSectionNode.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#include "ManualSectionNode.h"
-#include "ManualPageNode.h"
-#include <AK/DeprecatedString.h>
-#include <AK/LexicalPath.h>
-#include <AK/QuickSort.h>
-#include <LibCore/DirIterator.h>
-
-DeprecatedString ManualSectionNode::path() const
-{
- return DeprecatedString::formatted("/usr/share/man/man{}", m_section);
-}
-
-void ManualSectionNode::reify_if_needed() const
-{
- if (m_reified)
- return;
- m_reified = true;
-
- Core::DirIterator dir_iter { path(), Core::DirIterator::Flags::SkipDots };
-
- Vector<DeprecatedString> page_names;
- while (dir_iter.has_next()) {
- LexicalPath lexical_path(dir_iter.next_path());
- if (lexical_path.extension() != "md")
- continue;
- page_names.append(lexical_path.title());
- }
-
- quick_sort(page_names);
-
- for (auto& page_name : page_names)
- m_children.append(make<ManualPageNode>(*this, move(page_name)));
-}
-
-void ManualSectionNode::set_open(bool open)
-{
- if (m_open == open)
- return;
- m_open = open;
-}
diff --git a/Userland/Applications/Help/ManualSectionNode.h b/Userland/Applications/Help/ManualSectionNode.h
deleted file mode 100644
index ee7850f1db..0000000000
--- a/Userland/Applications/Help/ManualSectionNode.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
- * Copyright (c) 2022, the SerenityOS developers.
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#pragma once
-
-#include "ManualNode.h"
-
-class ManualSectionNode : public ManualNode {
-public:
- virtual ~ManualSectionNode() override = default;
-
- ManualSectionNode(DeprecatedString section, DeprecatedString name)
- : m_section(section)
- , m_full_name(DeprecatedString::formatted("{}. {}", section, name))
- {
- }
-
- virtual NonnullOwnPtrVector<ManualNode>& children() const override
- {
- reify_if_needed();
- return m_children;
- }
-
- virtual ManualNode const* parent() const override { return nullptr; }
- virtual DeprecatedString name() const override { return m_full_name; }
- virtual bool is_open() const override { return m_open; }
- void set_open(bool open);
-
- DeprecatedString const& section_name() const { return m_section; }
- DeprecatedString path() const;
-
-private:
- void reify_if_needed() const;
-
- DeprecatedString m_section;
- DeprecatedString m_full_name;
- mutable NonnullOwnPtrVector<ManualNode> m_children;
- mutable bool m_reified { false };
- bool m_open { false };
-};
diff --git a/Userland/Applications/Help/main.cpp b/Userland/Applications/Help/main.cpp
index d6db39b13d..52bd2ca23e 100644
--- a/Userland/Applications/Help/main.cpp
+++ b/Userland/Applications/Help/main.cpp
@@ -87,7 +87,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
auto main_widget = TRY(window->try_set_main_widget<MainWidget>());
TRY(main_widget->initialize_fallibles(window));
- main_widget->set_start_page(start_page, section);
+ TRY(main_widget->set_start_page(start_page, section));
window->show();
diff --git a/Userland/Libraries/CMakeLists.txt b/Userland/Libraries/CMakeLists.txt
index dde02378b1..c1e7ac418a 100644
--- a/Userland/Libraries/CMakeLists.txt
+++ b/Userland/Libraries/CMakeLists.txt
@@ -35,6 +35,7 @@ add_subdirectory(LibKeyboard)
add_subdirectory(LibLine)
add_subdirectory(LibLocale)
add_subdirectory(LibMain)
+add_subdirectory(LibManual)
add_subdirectory(LibMarkdown)
add_subdirectory(LibPartition)
add_subdirectory(LibPCIDB)
diff --git a/Userland/Libraries/LibManual/CMakeLists.txt b/Userland/Libraries/LibManual/CMakeLists.txt
new file mode 100644
index 0000000000..13f0454acb
--- /dev/null
+++ b/Userland/Libraries/LibManual/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(SOURCES
+ PageNode.cpp
+ SectionNode.cpp
+)
+
+serenity_lib(LibManual manual)
+target_link_libraries(LibManual PRIVATE LibCore)
diff --git a/Userland/Libraries/LibManual/Node.h b/Userland/Libraries/LibManual/Node.h
new file mode 100644
index 0000000000..232a7c3094
--- /dev/null
+++ b/Userland/Libraries/LibManual/Node.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * Copyright (c) 2022, the SerenityOS developers.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtrVector.h>
+#include <AK/RefCounted.h>
+#include <AK/String.h>
+#include <AK/StringView.h>
+
+namespace Manual {
+
+class PageNode;
+
+class Node : public RefCounted<Node> {
+public:
+ virtual ~Node() = default;
+
+ virtual NonnullRefPtrVector<Node>& children() const = 0;
+ virtual Node const* parent() const = 0;
+ virtual String name() const = 0;
+ virtual bool is_page() const { return false; }
+ virtual bool is_open() const { return false; }
+};
+
+}
diff --git a/Userland/Libraries/LibManual/PageNode.cpp b/Userland/Libraries/LibManual/PageNode.cpp
new file mode 100644
index 0000000000..0b44ee97c0
--- /dev/null
+++ b/Userland/Libraries/LibManual/PageNode.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * Copyright (c) 2022, the SerenityOS developers.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "PageNode.h"
+#include "SectionNode.h"
+
+namespace Manual {
+
+Node const* PageNode::parent() const
+{
+ return m_section.ptr();
+}
+
+NonnullRefPtrVector<Node>& PageNode::children() const
+{
+ static NonnullRefPtrVector<Node> empty_vector;
+ return empty_vector;
+}
+
+ErrorOr<String> PageNode::path() const
+{
+ return TRY(String::formatted("{}/{}.md", TRY(m_section->path()), m_page));
+}
+
+}
diff --git a/Userland/Libraries/LibManual/PageNode.h b/Userland/Libraries/LibManual/PageNode.h
new file mode 100644
index 0000000000..f74e78fb2f
--- /dev/null
+++ b/Userland/Libraries/LibManual/PageNode.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtr.h>
+#include <LibManual/Node.h>
+
+namespace Manual {
+
+class SectionNode;
+
+class PageNode : public Node {
+public:
+ virtual ~PageNode() override = default;
+
+ PageNode(NonnullRefPtr<SectionNode> section, String page)
+ : m_section(move(section))
+ , m_page(move(page))
+ {
+ }
+
+ virtual NonnullRefPtrVector<Node>& children() const override;
+ virtual Node const* parent() const override;
+ virtual String name() const override { return m_page; };
+ virtual bool is_page() const override { return true; }
+
+ ErrorOr<String> path() const;
+
+private:
+ NonnullRefPtr<SectionNode> m_section;
+ String m_page;
+};
+
+}
diff --git a/Userland/Libraries/LibManual/SectionNode.cpp b/Userland/Libraries/LibManual/SectionNode.cpp
new file mode 100644
index 0000000000..8ab6c89380
--- /dev/null
+++ b/Userland/Libraries/LibManual/SectionNode.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "SectionNode.h"
+#include "PageNode.h"
+#include <AK/LexicalPath.h>
+#include <AK/QuickSort.h>
+#include <LibCore/DirIterator.h>
+
+namespace Manual {
+
+ErrorOr<String> SectionNode::path() const
+{
+ return String::formatted("/usr/share/man/man{}", m_section);
+}
+
+ErrorOr<void> SectionNode::reify_if_needed() const
+{
+ if (m_reified)
+ return {};
+ m_reified = true;
+
+ Core::DirIterator dir_iter { TRY(path()).to_deprecated_string(), Core::DirIterator::Flags::SkipDots };
+
+ Vector<String> page_names;
+ while (dir_iter.has_next()) {
+ LexicalPath lexical_path(dir_iter.next_path());
+ if (lexical_path.extension() != "md")
+ continue;
+ page_names.append(TRY(String::from_utf8(lexical_path.title())));
+ }
+
+ quick_sort(page_names);
+
+ for (auto& page_name : page_names)
+ m_children.append(TRY(try_make_ref_counted<PageNode>(*this, move(page_name))));
+
+ return {};
+}
+
+void SectionNode::set_open(bool open)
+{
+ if (m_open == open)
+ return;
+ m_open = open;
+}
+
+Array<NonnullRefPtr<SectionNode>, number_of_sections> const sections = { {
+ make_ref_counted<SectionNode>("1"sv, "User Programs"sv),
+ make_ref_counted<SectionNode>("2"sv, "System Calls"sv),
+ make_ref_counted<SectionNode>("3"sv, "Library Functions"sv),
+ make_ref_counted<SectionNode>("4"sv, "Special Files"sv),
+ make_ref_counted<SectionNode>("5"sv, "File Formats"sv),
+ make_ref_counted<SectionNode>("6"sv, "Games"sv),
+ make_ref_counted<SectionNode>("7"sv, "Miscellanea"sv),
+ make_ref_counted<SectionNode>("8"sv, "Sysadmin Tools"sv),
+} };
+
+}
diff --git a/Userland/Libraries/LibManual/SectionNode.h b/Userland/Libraries/LibManual/SectionNode.h
new file mode 100644
index 0000000000..cc7546f8e1
--- /dev/null
+++ b/Userland/Libraries/LibManual/SectionNode.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * Copyright (c) 2022, the SerenityOS developers.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Error.h>
+#include <AK/String.h>
+#include <LibManual/Node.h>
+
+namespace Manual {
+
+class SectionNode : public Node {
+public:
+ virtual ~SectionNode() override = default;
+
+ SectionNode(StringView section, StringView name)
+ : m_section(MUST(String::from_utf8(section)))
+ , m_full_name(MUST(String::formatted("{}. {}", section, name)))
+ {
+ }
+
+ virtual NonnullRefPtrVector<Node>& children() const override
+ {
+ MUST(reify_if_needed());
+ return m_children;
+ }
+
+ virtual Node const* parent() const override { return nullptr; }
+ virtual String name() const override { return m_full_name; }
+ virtual bool is_open() const override { return m_open; }
+ void set_open(bool open);
+
+ String const& section_name() const { return m_section; }
+ ErrorOr<String> path() const;
+
+private:
+ ErrorOr<void> reify_if_needed() const;
+
+ String m_section;
+ String m_full_name;
+ mutable NonnullRefPtrVector<Node> m_children;
+ mutable bool m_reified { false };
+ bool m_open { false };
+};
+
+constexpr size_t number_of_sections = 8;
+
+extern Array<NonnullRefPtr<SectionNode>, number_of_sections> const sections;
+
+constexpr Array<StringView, number_of_sections> const section_numbers = {
+ "1"sv,
+ "2"sv,
+ "3"sv,
+ "4"sv,
+ "5"sv,
+ "6"sv,
+ "7"sv,
+ "8"sv,
+};
+
+}