From ad6a55e1f088f525b1bd2ee7e926fcafedead4c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?kleines=20Filmr=C3=B6llchen?= Date: Wed, 13 Jul 2022 00:20:27 +0200 Subject: [PATCH] Help+LibManual: Move non-UI-specific manual handling to LibManual This is a first step in deduplicating code within and across Help and man. Because LibManual also doesn't contain any DeprecatedString, some adjustments to Help's string handling is included, just to interoperate with LibManual better. Further work in this area mostly requires String APIs in LibGUI. --- Userland/Applications/Help/CMakeLists.txt | 5 +- Userland/Applications/Help/MainWidget.cpp | 80 +++++++-------- Userland/Applications/Help/MainWidget.h | 4 +- Userland/Applications/Help/ManualModel.cpp | 99 +++++++++++-------- Userland/Applications/Help/ManualModel.h | 12 +-- Userland/Applications/Help/ManualNode.h | 22 ----- Userland/Applications/Help/ManualPageNode.cpp | 25 ----- Userland/Applications/Help/ManualPageNode.h | 33 ------- .../Applications/Help/ManualSectionNode.cpp | 46 --------- .../Applications/Help/ManualSectionNode.h | 44 --------- Userland/Applications/Help/main.cpp | 2 +- Userland/Libraries/CMakeLists.txt | 1 + Userland/Libraries/LibManual/CMakeLists.txt | 7 ++ Userland/Libraries/LibManual/Node.h | 30 ++++++ Userland/Libraries/LibManual/PageNode.cpp | 29 ++++++ Userland/Libraries/LibManual/PageNode.h | 38 +++++++ Userland/Libraries/LibManual/SectionNode.cpp | 62 ++++++++++++ Userland/Libraries/LibManual/SectionNode.h | 65 ++++++++++++ 18 files changed, 339 insertions(+), 265 deletions(-) delete mode 100644 Userland/Applications/Help/ManualNode.h delete mode 100644 Userland/Applications/Help/ManualPageNode.cpp delete mode 100644 Userland/Applications/Help/ManualPageNode.h delete mode 100644 Userland/Applications/Help/ManualSectionNode.cpp delete mode 100644 Userland/Applications/Help/ManualSectionNode.h create mode 100644 Userland/Libraries/LibManual/CMakeLists.txt create mode 100644 Userland/Libraries/LibManual/Node.h create mode 100644 Userland/Libraries/LibManual/PageNode.cpp create mode 100644 Userland/Libraries/LibManual/PageNode.h create mode 100644 Userland/Libraries/LibManual/SectionNode.cpp create mode 100644 Userland/Libraries/LibManual/SectionNode.h 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 +#include +#include +#include #include #include #include @@ -30,6 +32,9 @@ #include #include #include +#include +#include +#include #include namespace Help { @@ -66,25 +71,25 @@ MainWidget::MainWidget() } auto& search_model = *static_cast(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("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 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 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 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 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 initialize_fallibles(GUI::Window&); - void set_start_page(StringView page, u32 section); + ErrorOr set_start_page(StringView page, u32 section); private: MainWidget(); void open_url(URL const&); - void open_page(DeprecatedString const& path); + void open_page(Optional 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 - -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 +#include +#include +#include ManualModel::ManualModel() { @@ -34,11 +24,14 @@ Optional 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(child_index.internal_data()); + auto* node = static_cast(child_index.internal_data()); if (!node->is_page()) continue; - auto* page = static_cast(node); - if (page->path() != path) + auto* page = static_cast(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 ManualModel::index_from_path(StringView path) const return {}; } -DeprecatedString ManualModel::page_name(const GUI::ModelIndex& index) const +Optional ManualModel::page_name(const GUI::ModelIndex& index) const { if (!index.is_valid()) return {}; - auto* node = static_cast(index.internal_data()); + auto* node = static_cast(index.internal_data()); if (!node->is_page()) return {}; - auto* page = static_cast(node); + auto* page = static_cast(node); return page->name(); } -DeprecatedString ManualModel::page_path(const GUI::ModelIndex& index) const +Optional ManualModel::page_path(const GUI::ModelIndex& index) const { if (!index.is_valid()) return {}; - auto* node = static_cast(index.internal_data()); + auto* node = static_cast(index.internal_data()); if (!node->is_page()) return {}; - auto* page = static_cast(node); - return page->path(); + auto* page = static_cast(node); + auto path = page->path(); + if (path.is_error()) + return {}; + return path.release_value(); } -ErrorOr ManualModel::page_view(DeprecatedString const& path) const +ErrorOr ManualModel::page_view(String const& path) const { if (path.is_empty()) return StringView {}; @@ -87,23 +83,29 @@ ErrorOr ManualModel::page_view(DeprecatedString const& path) const return view; } -DeprecatedString ManualModel::page_and_section(const GUI::ModelIndex& index) const +Optional ManualModel::page_and_section(const GUI::ModelIndex& index) const { if (!index.is_valid()) return {}; - auto* node = static_cast(index.internal_data()); + auto* node = static_cast(index.internal_data()); if (!node->is_page()) return {}; - auto* page = static_cast(node); - auto* section = static_cast(page->parent()); - return DeprecatedString::formatted("{}({})", page->name(), section->section_name()); + auto* page = static_cast(node); + auto* section = static_cast(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(parent_index.internal_data()); + return create_index(row, column, Manual::sections[row].ptr()); + auto* parent = static_cast(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(index.internal_data()); + auto* child = static_cast(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(index.internal_data()); + return sizeof(Manual::sections) / sizeof(Manual::sections[0]); + auto* node = static_cast(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(index.internal_data()); + auto* node = static_cast(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(index.internal_data()); + auto* node = static_cast(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 #include #include #include +#include #include class ManualModel final : public GUI::Model { @@ -23,10 +23,10 @@ public: Optional 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 page_view(DeprecatedString const& path) const; + Optional page_name(const GUI::ModelIndex&) const; + Optional page_path(const GUI::ModelIndex&) const; + Optional page_and_section(const GUI::ModelIndex&) const; + ErrorOr 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> m_mapped_files; + mutable HashMap> 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 - * Copyright (c) 2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include - -class ManualNode { -public: - virtual ~ManualNode() = default; - - virtual NonnullOwnPtrVector& 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 - * 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& ManualPageNode::children() const -{ - static NonnullOwnPtrVector 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 - * - * 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& 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 - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "ManualSectionNode.h" -#include "ManualPageNode.h" -#include -#include -#include -#include - -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 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(*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 - * 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& 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 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 serenity_main(Main::Arguments arguments) auto main_widget = TRY(window->try_set_main_widget()); 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 + * Copyright (c) 2022, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Manual { + +class PageNode; + +class Node : public RefCounted { +public: + virtual ~Node() = default; + + virtual NonnullRefPtrVector& 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 + * 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& PageNode::children() const +{ + static NonnullRefPtrVector empty_vector; + return empty_vector; +} + +ErrorOr 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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Manual { + +class SectionNode; + +class PageNode : public Node { +public: + virtual ~PageNode() override = default; + + PageNode(NonnullRefPtr section, String page) + : m_section(move(section)) + , m_page(move(page)) + { + } + + virtual NonnullRefPtrVector& 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 path() const; + +private: + NonnullRefPtr 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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "SectionNode.h" +#include "PageNode.h" +#include +#include +#include + +namespace Manual { + +ErrorOr SectionNode::path() const +{ + return String::formatted("/usr/share/man/man{}", m_section); +} + +ErrorOr 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 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(*this, move(page_name)))); + + return {}; +} + +void SectionNode::set_open(bool open) +{ + if (m_open == open) + return; + m_open = open; +} + +Array, number_of_sections> const sections = { { + make_ref_counted("1"sv, "User Programs"sv), + make_ref_counted("2"sv, "System Calls"sv), + make_ref_counted("3"sv, "Library Functions"sv), + make_ref_counted("4"sv, "Special Files"sv), + make_ref_counted("5"sv, "File Formats"sv), + make_ref_counted("6"sv, "Games"sv), + make_ref_counted("7"sv, "Miscellanea"sv), + make_ref_counted("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 + * Copyright (c) 2022, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +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& 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 path() const; + +private: + ErrorOr reify_if_needed() const; + + String m_section; + String m_full_name; + mutable NonnullRefPtrVector m_children; + mutable bool m_reified { false }; + bool m_open { false }; +}; + +constexpr size_t number_of_sections = 8; + +extern Array, number_of_sections> const sections; + +constexpr Array const section_numbers = { + "1"sv, + "2"sv, + "3"sv, + "4"sv, + "5"sv, + "6"sv, + "7"sv, + "8"sv, +}; + +}