// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/webui/shortcut_customization_ui/backend/search/search_concept_registry.h"
#include <string>
#include <vector>
#include "ash/public/mojom/accelerator_info.mojom.h"
#include "ash/webui/shortcut_customization_ui/backend/search/search_concept.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "chromeos/ash/components/local_search_service/public/cpp/local_search_service_proxy.h"
#include "chromeos/ash/components/local_search_service/shared_structs.h"
namespace ash::shortcut_ui {
SearchConceptRegistry::SearchConceptRegistry(
local_search_service::LocalSearchServiceProxy& local_search_service_proxy) {
local_search_service_proxy.GetIndex(
local_search_service::IndexId::kShortcutsApp,
local_search_service::Backend::kLinearMap,
index_remote_.BindNewPipeAndPassReceiver());
DCHECK(index_remote_.is_bound());
}
SearchConceptRegistry::~SearchConceptRegistry() = default;
void SearchConceptRegistry::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void SearchConceptRegistry::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void SearchConceptRegistry::SetSearchConcepts(
std::vector<SearchConcept> search_concepts) {
index_remote_->ClearIndex(base::BindOnce(
&SearchConceptRegistry::SetSearchConceptsHelper,
weak_ptr_factory_.GetWeakPtr(), std::move(search_concepts)));
}
void SearchConceptRegistry::SetSearchConceptsHelper(
std::vector<SearchConcept> search_concepts) {
// Reset the map since the search index has been reset.
result_id_to_search_concept_.clear();
std::vector<local_search_service::Data> local_search_service_data;
local_search_service_data.reserve(search_concepts.size());
for (auto& search_concept : search_concepts) {
// The SearchConcept should not have been added yet.
DCHECK(!result_id_to_search_concept_.contains(search_concept.id));
// Add this SearchConcept to the local map and register it with the Local
// Search Service index.
local_search_service_data.emplace_back(SearchConceptToData(search_concept));
result_id_to_search_concept_.insert(
{search_concept.id, std::move(search_concept)});
}
index_remote_->AddOrUpdate(
std::move(local_search_service_data),
base::BindOnce(&SearchConceptRegistry::NotifyRegistryUpdated,
weak_ptr_factory_.GetWeakPtr()));
}
void SearchConceptRegistry::NotifyRegistryUpdated() {
for (auto& observer : observer_list_) {
observer.OnRegistryUpdated();
}
}
const SearchConcept* SearchConceptRegistry::GetSearchConceptById(
const std::string& id) const {
const auto& matching_search_concept_iterator =
result_id_to_search_concept_.find(id);
if (matching_search_concept_iterator == result_id_to_search_concept_.end()) {
return nullptr;
}
return &matching_search_concept_iterator->second;
}
// Given a SearchConcept, return a Data object, which represents a single
// searchable item.
local_search_service::Data SearchConceptRegistry::SearchConceptToData(
const SearchConcept& search_concept) {
// One Data object contains a list of Content objects that can match against
// queries.
std::vector<local_search_service::Content> local_search_service_contents;
// Reserve the size for the vector since we insert once for the description
// and at most once more in the case of text accelerators.
local_search_service_contents.reserve(2);
// First, add this SearchConcept's description as searchable Content.
// Note that the Content ID is prefixed with the SearchConcept ID, since
// Content IDs have to be unique within an entire LSS index. However, it's not
// necessary to store this Content ID since it only be used internally to the
// LSS.
local_search_service_contents.emplace_back(
/*id=*/base::StrCat({search_concept.id, "-description"}),
/*content=*/search_concept.accelerator_layout_info->description);
// All SearchConcepts should contain at least one AcceleratorInfo.
DCHECK(search_concept.accelerator_infos.size() > 0);
// Get the first AcceleratorInfo to check if it's a text accelerator. Note
// that text accelerators should only have one entry in accelerator_infos.
const mojom::AcceleratorInfoPtr& first_accelerator_info =
search_concept.accelerator_infos.at(0);
// Only text accelerators should become searchable LSS Content.
if (first_accelerator_info->layout_properties->is_text_accelerator()) {
// Content->id needs to be unique across the entire index,
// so we prefix it with the SearchConcept's id.
std::string content_id =
base::StrCat({search_concept.id, "-text-accelerator"});
// To get the searchable part of a Text Accelerator, combine all of the
// TextAcceleratorParts into one string.
std::u16string content_string;
for (const auto& part :
first_accelerator_info->layout_properties->get_text_accelerator()
->parts) {
base::StrAppend(&content_string, {part->text});
}
local_search_service_contents.emplace_back(
/*id=*/content_id,
/*content=*/content_string);
}
return local_search_service::Data(
/*id=*/search_concept.id,
/*contents=*/std::move(local_search_service_contents));
}
} // namespace ash::shortcut_ui