Browse Source

Help: Add a search mode

This closes #2685, and brings some much needed nostalgia :^)
AnotherTest 5 năm trước cách đây
mục cha
commit
21094b4725
3 tập tin đã thay đổi với 104 bổ sung16 xóa
  1. 35 0
      Applications/Help/ManualModel.cpp
  2. 4 0
      Applications/Help/ManualModel.h
  3. 65 16
      Applications/Help/main.cpp

+ 35 - 0
Applications/Help/ManualModel.cpp

@@ -28,6 +28,9 @@
 #include "ManualNode.h"
 #include "ManualPageNode.h"
 #include "ManualSectionNode.h"
+#include <AK/ByteBuffer.h>
+#include <LibCore/File.h>
+#include <LibGUI/FilteringProxyModel.h>
 
 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<StringView, int> 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<MappedFile>(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<const ManualNode*>(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();

+ 4 - 0
Applications/Help/ManualModel.h

@@ -28,6 +28,7 @@
 
 #include <AK/NonnullRefPtr.h>
 #include <AK/Optional.h>
+#include <AK/Result.h>
 #include <AK/String.h>
 #include <LibGUI/Model.h>
 
@@ -44,11 +45,13 @@ public:
 
     String page_path(const GUI::ModelIndex&) const;
     String page_and_section(const GUI::ModelIndex&) const;
+    Result<StringView, int> 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<String, OwnPtr<MappedFile>> m_mapped_files;
 };

+ 65 - 16
Applications/Help/main.cpp

@@ -26,7 +26,6 @@
 
 #include "History.h"
 #include "ManualModel.h"
-#include <AK/ByteBuffer.h>
 #include <AK/URL.h>
 #include <LibCore/File.h>
 #include <LibDesktop/Launcher.h>
@@ -34,11 +33,14 @@
 #include <LibGUI/Action.h>
 #include <LibGUI/Application.h>
 #include <LibGUI/BoxLayout.h>
+#include <LibGUI/FilteringProxyModel.h>
+#include <LibGUI/ListView.h>
 #include <LibGUI/Menu.h>
 #include <LibGUI/MenuBar.h>
 #include <LibGUI/MessageBox.h>
 #include <LibGUI/Splitter.h>
-#include <LibGUI/TextEditor.h>
+#include <LibGUI/TabWidget.h>
+#include <LibGUI/TextBox.h>
 #include <LibGUI/ToolBar.h>
 #include <LibGUI/ToolBarContainer.h>
 #include <LibGUI/TreeView.h>
@@ -100,10 +102,41 @@ int main(int argc, char* argv[])
 
     auto model = ManualModel::create();
 
-    auto& tree_view = splitter.add<GUI::TreeView>();
+    auto& left_tab_bar = splitter.add<GUI::TabWidget>();
+    auto& tree_view = left_tab_bar.add_tab<GUI::TreeView>("Tree");
+    auto& search_view = left_tab_bar.add_tab<GUI::Widget>("Search");
+    search_view.set_layout<GUI::VerticalBoxLayout>();
+    auto& search_box = search_view.add<GUI::TextBox>();
+    auto& search_list_view = search_view.add<GUI::ListView>();
+    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<GUI::FilteringProxyModel*>(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<GUI::FilteringProxyModel*>(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<Web::PageView>();
 
@@ -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<GUI::FilteringProxyModel*>(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();