diff --git a/Applications/Help/ManualModel.cpp b/Applications/Help/ManualModel.cpp index c3979e8449e..092a2b99b98 100644 --- a/Applications/Help/ManualModel.cpp +++ b/Applications/Help/ManualModel.cpp @@ -28,6 +28,9 @@ #include "ManualNode.h" #include "ManualPageNode.h" #include "ManualSectionNode.h" +#include +#include +#include static ManualSectionNode s_sections[] = { { "1", "Command-line programs" }, @@ -76,6 +79,25 @@ String ManualModel::page_path(const GUI::ModelIndex& index) const return page->path(); } +Result ManualModel::page_view(const String& path) const +{ + if (path.is_empty()) + return StringView {}; + + auto mapped_file = m_mapped_files.get(path); + if (mapped_file.has_value()) + return StringView { (const char*)mapped_file.value()->data(), mapped_file.value()->size() }; + + auto map = make(path); + if (!map->is_valid()) + return map->errno_if_invalid(); + + StringView view { (const char*)map->data(), map->size() }; + m_mapped_files.set(path, move(map)); + + return view; +} + String ManualModel::page_and_section(const GUI::ModelIndex& index) const { if (!index.is_valid()) @@ -137,6 +159,10 @@ GUI::Variant ManualModel::data(const GUI::ModelIndex& index, Role role) const { auto* node = static_cast(index.internal_data()); switch (role) { + case Role::Search: + if (!node->is_page()) + return {}; + return String(page_view(page_path(index)).value()); case Role::Display: return node->name(); case Role::Icon: @@ -156,6 +182,15 @@ void ManualModel::update_section_node_on_toggle(const GUI::ModelIndex& index, co node->set_open(open); } +TriState ManualModel::data_matches(const GUI::ModelIndex& index, GUI::Variant term) const +{ + auto view_result = page_view(page_path(index)); + if (view_result.is_error() || view_result.value().is_empty()) + return TriState::False; + + return view_result.value().contains(term.as_string()) ? TriState::True : TriState::False; +} + void ManualModel::update() { did_update(); diff --git a/Applications/Help/ManualModel.h b/Applications/Help/ManualModel.h index f1d9ddf0ebc..f235b59c203 100644 --- a/Applications/Help/ManualModel.h +++ b/Applications/Help/ManualModel.h @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -44,11 +45,13 @@ public: String page_path(const GUI::ModelIndex&) const; String page_and_section(const GUI::ModelIndex&) const; + Result page_view(const String& path) const; void update_section_node_on_toggle(const GUI::ModelIndex&, const bool); virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override; virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override; virtual GUI::Variant data(const GUI::ModelIndex&, Role = Role::Display) const override; + virtual TriState data_matches(const GUI::ModelIndex&, GUI::Variant) const override; virtual void update() override; virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override; virtual GUI::ModelIndex index(int row, int column = 0, const GUI::ModelIndex& parent = GUI::ModelIndex()) const override; @@ -59,4 +62,5 @@ private: GUI::Icon m_section_open_icon; GUI::Icon m_section_icon; GUI::Icon m_page_icon; + mutable HashMap> m_mapped_files; }; diff --git a/Applications/Help/main.cpp b/Applications/Help/main.cpp index 28dfb6b5cf4..cdaba4d0835 100644 --- a/Applications/Help/main.cpp +++ b/Applications/Help/main.cpp @@ -26,7 +26,6 @@ #include "History.h" #include "ManualModel.h" -#include #include #include #include @@ -34,11 +33,14 @@ #include #include #include +#include +#include #include #include #include #include -#include +#include +#include #include #include #include @@ -100,10 +102,41 @@ int main(int argc, char* argv[]) auto model = ManualModel::create(); - auto& tree_view = splitter.add(); + auto& left_tab_bar = splitter.add(); + auto& tree_view = left_tab_bar.add_tab("Tree"); + auto& search_view = left_tab_bar.add_tab("Search"); + search_view.set_layout(); + auto& search_box = search_view.add(); + auto& search_list_view = search_view.add(); + search_box.set_preferred_size(0, 20); + search_box.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); + search_box.set_text("Search..."); + search_box.on_focusin = [&] { + if (search_box.text() == "Search...") + search_box.set_text(""); + }; + search_box.on_change = [&] { + if (auto model = search_list_view.model()) { + auto& search_model = *static_cast(model); + search_model.set_filter_term(search_box.text()); + search_model.update(); + } + }; + search_box.on_focusout = [&] { + if (search_box.text().is_empty()) { + if (auto model = search_list_view.model()) { + auto& search_model = *static_cast(model); + search_model.set_filter_term(""); + } + search_box.set_text("Search..."); + } + }; + search_list_view.set_model(GUI::FilteringProxyModel::construct(model)); + search_list_view.model()->update(); + tree_view.set_model(model); - tree_view.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); - tree_view.set_preferred_size(200, 500); + left_tab_bar.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); + left_tab_bar.set_preferred_size(200, 500); auto& page_view = splitter.add(); @@ -123,19 +156,13 @@ int main(int argc, char* argv[]) return; } - dbg() << "Opening page at " << path; - - auto file = Core::File::construct(); - file->set_filename(path); - - if (!file->open(Core::IODevice::OpenMode::ReadOnly)) { - int saved_errno = errno; - GUI::MessageBox::show(window, strerror(saved_errno), "Failed to open man page", GUI::MessageBox::Type::Error); + auto source_result = model->page_view(path); + if (source_result.is_error()) { + GUI::MessageBox::show(window, strerror(source_result.error()), "Failed to open man page", GUI::MessageBox::Type::Error); return; } - auto buffer = file->read_all(); - StringView source { (const char*)buffer.data(), buffer.size() }; + auto source = source_result.value(); String html; { auto md_document = Markdown::Document::parse(source); @@ -173,6 +200,28 @@ int main(int argc, char* argv[]) GUI::MessageBox::Type::Error); } }; + search_list_view.on_selection = [&](auto index) { + if (!index.is_valid()) + return; + + if (auto model = search_list_view.model()) { + auto& search_model = *static_cast(model); + index = search_model.map(index); + } else { + page_view.set_document(nullptr); + return; + } + String path = model->page_path(index); + if (path.is_null()) { + page_view.set_document(nullptr); + return; + } + tree_view.selection().clear(); + tree_view.selection().add(index); + history.push(path); + update_actions(); + open_page(path); + }; page_view.on_link_click = [&](auto& url, auto&, unsigned) { if (url.protocol() != "file") { @@ -230,7 +279,7 @@ int main(int argc, char* argv[]) app->set_menubar(move(menubar)); - window->set_focused_widget(&tree_view); + window->set_focused_widget(&left_tab_bar); window->show(); return app->exec();