Fuzzy finder

This commit is contained in:
Javier Feliz 2025-08-28 17:03:32 -04:00
parent 60f2be7f50
commit 244ff7cb4d
14 changed files with 864 additions and 82 deletions

View File

@ -28,10 +28,12 @@ qt_add_executable(waycast)
include_directories("${CMAKE_SOURCE_DIR}/lib")
include_directories("${CMAKE_SOURCE_DIR}/lib/ui")
include_directories("${CMAKE_SOURCE_DIR}/lib/plugins")
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/src/*.cpp")
file(GLOB_RECURSE LIB_UI_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/lib/ui/*.cpp")
target_sources(waycast PRIVATE ${SRC_FILES} ${LIB_UI_FILES} src/main.cpp)
target_include_directories(waycast PRIVATE ${CMAKE_SOURCE_DIR}/lib)
file(GLOB_RECURSE PLUGIN_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/src/plugins/**/*.cpp")
target_sources(waycast PRIVATE ${SRC_FILES} ${LIB_UI_FILES} ${PLUGIN_FILES} src/main.cpp)
target_include_directories(waycast PRIVATE ${CMAKE_SOURCE_DIR}/lib ${CMAKE_SOURCE_DIR}/lib/plugins ${CMAKE_SOURCE_DIR}/src)
# target_include_directories(waycast PRIVATE ${CMAKE_SOURCE_DIR}/lib ${CMAKE_CURRENT_BINARY_DIR})
# Bundle QML into the app (no runtime QML path headaches)
@ -62,7 +64,7 @@ endif()
# Add -fmodules-ts where needed
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(waycast PRIVATE -fmodules-ts)
target_compile_options(waycast PRIVATE -fmodules-ts -mavx2)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_compile_options(waycast PRIVATE -fmodules-ts)
target_compile_options(waycast PRIVATE -fmodules-ts -mavx2)
endif()

View File

@ -7,6 +7,7 @@
#include <iostream>
#include <sstream>
#include <fstream>
#include <functional>
namespace files
{
@ -40,6 +41,61 @@ namespace files
return out;
}
inline std::vector<fs::path> findAllFiles(const std::string& path, int max_depth = -1)
{
std::vector<fs::path> out;
std::error_code ec;
fs::path p(path);
if (!fs::exists(p, ec) || !fs::is_directory(p, ec))
return out;
// Use recursive_directory_iterator with depth control
auto options = fs::directory_options::skip_permission_denied;
try {
if (max_depth < 0) {
// No depth limit - use unlimited recursion
for (const auto& entry : fs::recursive_directory_iterator(p, options, ec)) {
if (ec) {
ec.clear();
continue;
}
if (entry.is_regular_file(ec) && !ec) {
out.push_back(entry.path());
}
ec.clear();
}
} else {
// Limited depth recursion
std::function<void(const fs::path&, int)> recurse = [&](const fs::path& dir_path, int current_depth) {
if (current_depth > max_depth) return;
for (const auto& entry : fs::directory_iterator(dir_path, options, ec)) {
if (ec) {
ec.clear();
continue;
}
if (entry.is_regular_file(ec) && !ec) {
out.push_back(entry.path());
} else if (entry.is_directory(ec) && !ec && current_depth < max_depth) {
recurse(entry.path(), current_depth + 1);
}
ec.clear();
}
};
recurse(p, 0);
}
} catch (const fs::filesystem_error&) {
// Handle any remaining filesystem errors silently
}
return out;
}
inline std::string readFile(const std::string &filename)
{
std::ifstream in(filename);

326
lib/fuzzy.hpp Normal file
View File

@ -0,0 +1,326 @@
#include <string>
#include <filesystem>
#include <vector>
#include <algorithm>
#include <type_traits>
#include <immintrin.h> // AVX/AVX2 intrinsics
namespace fuzzy
{
struct FuzzyMatch
{
std::string text;
int score;
std::vector<size_t> matchPositions;
bool operator<(const FuzzyMatch &other) const
{
return score > other.score; // Higher score = better match
}
};
class FuzzyFinder
{
public:
// Find method for string candidates
std::vector<FuzzyMatch> find(const std::vector<std::string> &candidates,
const std::string &query,
int limit = 10)
{
return findInternal(candidates, query, limit,
[](const std::string &candidate) -> std::string
{
return candidate;
});
}
// Find method for filesystem path candidates
std::vector<FuzzyMatch> find(const std::vector<std::filesystem::path> &candidates,
const std::string &query,
int limit = 10)
{
return findInternal(candidates, query, limit,
[](const std::filesystem::path &candidate) -> std::string
{
// For now, just use filename for matching
// In the future, could implement path-specific scoring
return candidate.filename().string();
});
}
private:
// Generic internal find method using a converter function
template <typename T, typename Converter>
std::vector<FuzzyMatch> findInternal(const std::vector<T> &candidates,
const std::string &query,
int limit,
Converter converter)
{
if (query.empty())
{
return {};
}
std::vector<FuzzyMatch> matches;
matches.reserve(candidates.size());
for (const auto &candidate : candidates)
{
std::string searchText = converter(candidate);
auto match = calculateMatch(searchText, query);
if (match.score > 0)
{
// Store the original candidate as text for display
if constexpr (std::is_same_v<T, std::filesystem::path>)
{
match.text = candidate.string(); // Full path for display
}
else
{
match.text = searchText; // Use converted text
}
matches.push_back(std::move(match));
}
}
// Sort by score (highest first)
std::sort(matches.begin(), matches.end());
// Limit results
if (limit > 0 && matches.size() > static_cast<size_t>(limit))
{
matches.resize(limit);
}
return matches;
}
// Check if AVX2 is available at runtime
bool hasAVX2() const
{
return __builtin_cpu_supports("avx2");
}
// AVX2-accelerated character search - finds next occurrence of query_char in text
size_t findNextCharAVX2(const std::string &text, char query_char, size_t start_pos) const
{
const size_t text_len = text.length();
if (start_pos >= text_len)
return std::string::npos;
const char *text_ptr = text.c_str() + start_pos;
const size_t remaining = text_len - start_pos;
// Process 32 characters at a time with AVX2
const size_t avx_chunks = remaining / 32;
const __m256i query_vec = _mm256_set1_epi8(query_char);
for (size_t chunk = 0; chunk < avx_chunks; ++chunk)
{
const __m256i text_vec = _mm256_loadu_si256(reinterpret_cast<const __m256i *>(text_ptr + chunk * 32));
const __m256i cmp_result = _mm256_cmpeq_epi8(query_vec, text_vec);
const uint32_t match_mask = _mm256_movemask_epi8(cmp_result);
if (match_mask != 0)
{
// Found a match - find the first set bit
const int first_match = __builtin_ctz(match_mask);
return start_pos + chunk * 32 + first_match;
}
}
// Handle remaining characters with scalar code
const size_t remaining_start = start_pos + avx_chunks * 32;
for (size_t i = remaining_start; i < text_len; ++i)
{
if (text[i] == query_char)
{
return i;
}
}
return std::string::npos;
}
FuzzyMatch calculateMatch(const std::string &text, const std::string &query)
{
// Choose algorithm based on AVX2 availability and string length
if (hasAVX2() && text.length() > 64 && query.length() > 2)
{
return calculateMatchAVX2(text, query);
}
else
{
return calculateMatchScalar(text, query);
}
}
// AVX2-accelerated fuzzy matching
FuzzyMatch calculateMatchAVX2(const std::string &text, const std::string &query)
{
FuzzyMatch match;
match.text = text;
match.score = 0;
if (query.empty() || text.empty())
{
return match;
}
std::string lowerText = toLower(text);
std::string lowerQuery = toLower(query);
size_t textIdx = 0;
size_t queryIdx = 0;
int consecutiveBonus = 0;
bool inConsecutiveMatch = false;
// Use AVX2 to accelerate character searching
while (queryIdx < lowerQuery.length())
{
char queryChar = lowerQuery[queryIdx];
// Use AVX2 to find next occurrence of query character
size_t foundPos = findNextCharAVX2(lowerText, queryChar, textIdx);
if (foundPos == std::string::npos)
{
break; // Character not found
}
textIdx = foundPos;
match.matchPositions.push_back(textIdx);
// Calculate score (same logic as before)
int charScore = 10;
if (inConsecutiveMatch && foundPos == match.matchPositions[match.matchPositions.size() - 2] + 1)
{
consecutiveBonus += 5;
charScore += consecutiveBonus;
}
else
{
consecutiveBonus = 0;
inConsecutiveMatch = true;
}
if (textIdx == 0 || !std::isalnum(lowerText[textIdx - 1]))
{
charScore += 15;
}
if (text[textIdx] == query[queryIdx])
{
charScore += 5;
}
match.score += charScore;
queryIdx++;
textIdx++; // Move past current match
}
// Apply penalties and validation
if (queryIdx < lowerQuery.length())
{
match.score -= (lowerQuery.length() - queryIdx) * 3;
}
if (queryIdx < lowerQuery.length())
{
match.score = 0;
match.matchPositions.clear();
}
return match;
}
// Original scalar implementation as fallback
FuzzyMatch calculateMatchScalar(const std::string &text, const std::string &query)
{
FuzzyMatch match;
match.text = text;
match.score = 0;
if (query.empty() || text.empty())
{
return match;
}
std::string lowerText = toLower(text);
std::string lowerQuery = toLower(query);
// Simple fuzzy matching algorithm
size_t textIdx = 0;
size_t queryIdx = 0;
int consecutiveBonus = 0;
bool inConsecutiveMatch = false;
while (textIdx < lowerText.length() && queryIdx < lowerQuery.length())
{
if (lowerText[textIdx] == lowerQuery[queryIdx])
{
match.matchPositions.push_back(textIdx);
// Base score for character match
int charScore = 10;
// Bonus for consecutive characters
if (inConsecutiveMatch)
{
consecutiveBonus += 5;
charScore += consecutiveBonus;
}
else
{
consecutiveBonus = 0;
inConsecutiveMatch = true;
}
// Bonus for word boundaries (start of word)
if (textIdx == 0 || !std::isalnum(lowerText[textIdx - 1]))
{
charScore += 15;
}
// Bonus for exact case match
if (text[textIdx] == query[queryIdx])
{
charScore += 5;
}
match.score += charScore;
queryIdx++;
}
else
{
inConsecutiveMatch = false;
consecutiveBonus = 0;
}
textIdx++;
}
// Penalty for unmatched characters in query
if (queryIdx < lowerQuery.length())
{
match.score -= (lowerQuery.length() - queryIdx) * 3;
}
// If we didn't match all query characters, score is 0
if (queryIdx < lowerQuery.length())
{
match.score = 0;
match.matchPositions.clear();
}
return match;
}
std::string toLower(const std::string &str)
{
std::string result = str;
std::transform(result.begin(), result.end(), result.begin(), ::tolower);
return result;
}
};
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "../ui/ListItem.hpp"
#include <vector>
#include <memory>
#include <QString>
namespace plugins
{
class SearchPlugin
{
public:
virtual ~SearchPlugin() = default;
// Search for items based on query
virtual std::vector<ListItemPtr> search(const QString &query) = 0;
// Get all items (used for initial display or empty query)
virtual std::vector<ListItemPtr> getAllItems() = 0;
// Plugin identification
virtual QString pluginName() const = 0;
virtual QString pluginDescription() const = 0;
// Priority for ordering results (higher = higher priority)
virtual int priority() const { return 0; }
// Whether this plugin should be active by default
virtual bool isEnabled() const { return true; }
};
using SearchPluginPtr = std::unique_ptr<SearchPlugin>;
}

View File

@ -0,0 +1,77 @@
#pragma once
#include "PluginInterface.hpp"
#include "../ui/ListItem.hpp"
#include <vector>
#include <memory>
#include <algorithm>
namespace plugins
{
class PluginManager
{
public:
static PluginManager& instance()
{
static PluginManager instance;
return instance;
}
void registerPlugin(SearchPluginPtr plugin)
{
if (plugin && plugin->isEnabled())
{
m_plugins.push_back(std::move(plugin));
// Sort plugins by priority (higher priority first)
std::sort(m_plugins.begin(), m_plugins.end(),
[](const SearchPluginPtr& a, const SearchPluginPtr& b) {
return a->priority() > b->priority();
});
}
}
std::vector<ListItemPtr> search(const QString& query)
{
std::vector<ListItemPtr> allResults;
for (const auto& plugin : m_plugins)
{
if (query.isEmpty())
{
auto items = plugin->getAllItems();
allResults.insert(allResults.end(), items.begin(), items.end());
}
else
{
auto results = plugin->search(query);
allResults.insert(allResults.end(), results.begin(), results.end());
}
}
return allResults;
}
std::vector<ListItemPtr> getAllItems()
{
std::vector<ListItemPtr> allItems;
for (const auto& plugin : m_plugins)
{
auto items = plugin->getAllItems();
allItems.insert(allItems.end(), items.begin(), items.end());
}
return allItems;
}
const std::vector<SearchPluginPtr>& getPlugins() const
{
return m_plugins;
}
private:
PluginManager() = default;
std::vector<SearchPluginPtr> m_plugins;
};
}

View File

@ -1,5 +1,5 @@
#include "AppListModel.hpp"
#include "DesktopAppListItem.hpp"
#include "../plugins/PluginManager.hpp"
#include <QDebug>
#undef signals
@ -58,8 +58,9 @@ void AppListModel::loadItems()
beginResetModel();
m_items.clear();
// Add desktop applications
addDesktopApps();
// Get items from all registered plugins
auto& pluginManager = plugins::PluginManager::instance();
m_items = pluginManager.getAllItems();
updateFilteredItems();
endResetModel();
@ -91,20 +92,19 @@ void AppListModel::setSearchText(const QString &searchText)
emit searchTextChanged();
beginResetModel();
// Use plugin manager for search
auto& pluginManager = plugins::PluginManager::instance();
m_items = pluginManager.search(searchText);
updateFilteredItems();
endResetModel();
}
void AppListModel::addDesktopApps()
{
dmenu::DEVec apps = dmenu::get_dmenu_app_data();
if (!apps)
return;
for (const auto &entry : *apps) {
auto item = std::make_shared<DesktopAppListItem>(entry);
m_items.push_back(item);
}
// This method is deprecated - plugins now handle data loading
// Kept for backward compatibility but does nothing
}
void AppListModel::addItems(const std::vector<ListItemPtr> &items)
@ -118,11 +118,8 @@ void AppListModel::updateFilteredItems()
{
m_filteredIndexes.clear();
// Since PluginManager now handles filtering, just show all items
for (size_t i = 0; i < m_items.size(); ++i) {
const ListItemPtr &item = m_items[i];
if (item->matches(m_searchText)) {
m_filteredIndexes.push_back(static_cast<int>(i));
}
m_filteredIndexes.push_back(static_cast<int>(i));
}
}

View File

@ -28,7 +28,7 @@ public:
Q_INVOKABLE void executeItem(int index);
// Add items from different sources
void addDesktopApps();
void addDesktopApps(); // Deprecated - use PluginManager instead
void addItems(const std::vector<ListItemPtr> &items);
QString searchText() const;

View File

@ -0,0 +1,89 @@
#pragma once
#include "ListItem.hpp"
#include <functional>
#include <QProcess>
// Generic implementation that plugin developers can use without writing boilerplate
class GenericListItem : public ListItem
{
public:
GenericListItem(const QString& name,
const QString& description,
const QUrl& iconUrl,
const QString& itemType,
std::function<void()> executeFunc = nullptr)
: m_name(name)
, m_description(description)
, m_iconUrl(iconUrl)
, m_itemType(itemType)
, m_executeFunc(executeFunc)
{
}
QString name() const override { return m_name; }
QString description() const override { return m_description; }
QUrl iconUrl() const override { return m_iconUrl; }
QString itemType() const override { return m_itemType; }
void execute() override
{
if (m_executeFunc) {
m_executeFunc();
}
}
// Allow updating properties after construction
void setName(const QString& name) { m_name = name; }
void setDescription(const QString& description) { m_description = description; }
void setIconUrl(const QUrl& iconUrl) { m_iconUrl = iconUrl; }
void setExecuteFunction(std::function<void()> func) { m_executeFunc = func; }
private:
QString m_name;
QString m_description;
QUrl m_iconUrl;
QString m_itemType;
std::function<void()> m_executeFunc;
};
// Convenience factory functions for common use cases
namespace ListItems {
// Create an application-style item
inline ListItemPtr createApplication(const QString& name,
const QString& exec,
const QUrl& icon = QUrl())
{
return std::make_shared<GenericListItem>(
name, exec, icon, "app",
[exec]() {
// Basic app execution - plugins can override for complex logic
QProcess::startDetached("/bin/sh", QStringList() << "-c" << exec);
}
);
}
// Create a file-style item
inline ListItemPtr createFile(const QString& filename,
const QString& path,
const QUrl& icon = QUrl())
{
return std::make_shared<GenericListItem>(
filename, path, icon, "file",
[path]() {
QProcess::startDetached("xdg-open", QStringList() << path);
}
);
}
// Create a generic item with custom action
inline ListItemPtr createItem(const QString& name,
const QString& description,
const QString& itemType,
std::function<void()> action = nullptr,
const QUrl& icon = QUrl())
{
return std::make_shared<GenericListItem>(name, description, icon, itemType, action);
}
}

View File

@ -9,7 +9,11 @@
#include "files.hpp"
#define signals public
#include "fuzzy.hpp"
#include "ui/AppListModel.hpp"
#include "plugins/PluginManager.hpp"
#include "plugins/DesktopAppPlugin.hpp"
#include <cstdlib>
#include <string>
#include <iostream>
@ -18,67 +22,67 @@
#include <sstream>
#include <filesystem>
std::vector<std::string> split(std::string s, char delimiter)
{
std::vector<std::string> items;
std::string line;
std::stringstream ss(s);
while (std::getline(ss, line, delimiter))
{
items.push_back(line);
}
return items;
}
int main(int argc, char *argv[])
{
// dmenu::DEVec apps = dmenu::get_dmenu_app_data();
// for (auto &app : *apps.get())
// {
// std::cout << app.iconPath() << std::endl;
// // std::cout << std::format("---\nName: {}\nID: {}\nIcon: {}\nExec: {}\nDisp: {}\n---\n", app.id, app.name, app.iconPath(), app.exec, app.display ? "yes" : "no");
// }
QGuiApplication app(argc, argv);
QCoreApplication::setApplicationName("waycast");
// Enable system theme support
app.setDesktopSettingsAware(true);
// Ensure we follow system color scheme
app.setAttribute(Qt::AA_UseStyleSheetPropagationInWidgetStyles, true);
QQmlApplicationEngine engine;
// Register the AppListModel type with QML
qmlRegisterType<AppListModel>("WayCast", 1, 0, "AppListModel");
// Set up layer shell before creating any windows
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []()
{ QCoreApplication::exit(-1); });
engine.loadFromModule("WayCast", "Main");
// Get the root objects and configure layer shell
auto rootObjects = engine.rootObjects();
if (!rootObjects.isEmpty())
fuzzy::FuzzyFinder fuzzy;
std::string home = std::getenv("HOME");
std::cout << "Home Directory: " << home << '\n';
auto projects = files::findAllFiles(home.append("/Documents/wallpapers"), 1);
auto pathMatches = fuzzy.find(projects, "sun", 10);
std::cout << "Path fuzzy search for 'anime':\n";
for (const auto &match : pathMatches)
{
QWindow *window = qobject_cast<QWindow *>(rootObjects.first());
if (window)
{
LayerShellQt::Window *layerWindow = LayerShellQt::Window::get(window);
if (layerWindow)
{
layerWindow->setLayer(LayerShellQt::Window::LayerTop);
layerWindow->setAnchors({});
layerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityOnDemand);
// Now show the window after layer shell is configured
window->show();
}
}
std::cout << " " << match.text << " (score: " << match.score << ")\n";
}
return app.exec();
std::cout << "\nAll files found:\n";
for (const auto &p : projects)
{
std::cout << " " << p.string() << std::endl;
}
// ACTUAL APP CODE
// QGuiApplication app(argc, argv);
// QCoreApplication::setApplicationName("waycast");
// // Initialize plugin system
// auto& pluginManager = plugins::PluginManager::instance();
// pluginManager.registerPlugin(std::make_unique<plugins::DesktopAppPlugin>());
// // Enable system theme support
// app.setDesktopSettingsAware(true);
// // Ensure we follow system color scheme
// app.setAttribute(Qt::AA_UseStyleSheetPropagationInWidgetStyles, true);
// QQmlApplicationEngine engine;
// // Register the AppListModel type with QML
// qmlRegisterType<AppListModel>("WayCast", 1, 0, "AppListModel");
// // Set up layer shell before creating any windows
// QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []()
// { QCoreApplication::exit(-1); });
// engine.loadFromModule("WayCast", "Main");
// // Get the root objects and configure layer shell
// auto rootObjects = engine.rootObjects();
// if (!rootObjects.isEmpty())
// {
// QWindow *window = qobject_cast<QWindow *>(rootObjects.first());
// if (window)
// {
// LayerShellQt::Window *layerWindow = LayerShellQt::Window::get(window);
// if (layerWindow)
// {
// layerWindow->setLayer(LayerShellQt::Window::LayerTop);
// layerWindow->setAnchors({});
// layerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityOnDemand);
// // Now show the window after layer shell is configured
// window->show();
// }
// }
// }
// return app.exec();
}

View File

@ -0,0 +1,67 @@
#pragma once
#include "../../lib/plugins/PluginInterface.hpp"
#include "../../lib/dmenu.hpp"
#include "DesktopAppPlugin/DesktopAppListItem.hpp"
#include <algorithm>
namespace plugins
{
class DesktopAppPlugin : public SearchPlugin
{
public:
DesktopAppPlugin()
{
loadDesktopEntries();
}
std::vector<ListItemPtr> search(const QString &query) override
{
std::vector<ListItemPtr> results;
for (const auto &item : m_allItems)
{
if (item->matches(query))
{
results.push_back(item);
}
}
return results;
}
std::vector<ListItemPtr> getAllItems() override
{
return m_allItems;
}
QString pluginName() const override
{
return "Desktop Applications";
}
QString pluginDescription() const override
{
return "Searches installed desktop applications";
}
int priority() const override
{
return 100; // High priority for applications
}
private:
void loadDesktopEntries()
{
auto desktopEntries = dmenu::get_dmenu_app_data();
for (const auto &entry : *desktopEntries)
{
auto listItem = std::make_shared<DesktopAppListItem>(entry);
m_allItems.push_back(listItem);
}
}
std::vector<ListItemPtr> m_allItems;
};
}

View File

@ -1,9 +1,9 @@
#pragma once
#include "ListItem.hpp"
#include "../../../lib/ui/ListItem.hpp"
#undef signals
#include "../dmenu.hpp"
#include "../../../lib/dmenu.hpp"
#define signals public
class DesktopAppListItem : public ListItem

View File

@ -0,0 +1,54 @@
#pragma once
#include "../../lib/plugins/PluginInterface.hpp"
#include "../../lib/ui/GenericListItem.hpp"
namespace plugins
{
// Example of how simple it is to create a new plugin
class ExamplePlugin : public SearchPlugin
{
public:
std::vector<ListItemPtr> search(const QString& query) override
{
std::vector<ListItemPtr> results;
// Example: Create some sample items that match any query
if (query.contains("test", Qt::CaseInsensitive)) {
// Using the convenient factory function
results.push_back(ListItems::createItem(
"Test Item 1",
"This is a test item",
"example",
[]() { /* custom action */ }
));
// Or create directly with GenericListItem
results.push_back(std::make_shared<GenericListItem>(
"Test Item 2",
"Another test item",
QUrl(), // no icon
"example",
[]() {
// Custom execute action
qDebug() << "Test item executed!";
}
));
}
return results;
}
std::vector<ListItemPtr> getAllItems() override
{
// Return some default items
return {
ListItems::createItem("Example Item", "Always visible", "example")
};
}
QString pluginName() const override { return "Example Plugin"; }
QString pluginDescription() const override { return "Demonstrates easy plugin creation"; }
int priority() const override { return 10; } // Low priority
};
}

View File

@ -0,0 +1,77 @@
#pragma once
#include "../../lib/plugins/PluginInterface.hpp"
#include "../../lib/files.hpp"
#include "../../lib/ui/GenericListItem.hpp"
#include <QUrl>
#include <QProcess>
#include <QDir>
#include <filesystem>
namespace plugins
{
class FileSystemPlugin : public SearchPlugin
{
public:
FileSystemPlugin(const QString& searchPath = QDir::homePath())
: m_searchPath(searchPath.toStdString())
{
}
std::vector<ListItemPtr> search(const QString& query) override
{
std::vector<ListItemPtr> results;
if (query.length() < 2) // Avoid expensive searches for short queries
return results;
try {
for (const auto& entry : std::filesystem::recursive_directory_iterator(m_searchPath)) {
if (entry.is_regular_file()) {
auto filename = QString::fromStdString(entry.path().filename().string());
if (filename.contains(query, Qt::CaseInsensitive)) {
auto item = ListItems::createFile(
filename,
QString::fromStdString(entry.path().string())
);
results.push_back(item);
if (results.size() >= 50) // Limit results to avoid performance issues
break;
}
}
}
} catch (const std::filesystem::filesystem_error&) {
// Handle permission errors silently
}
return results;
}
std::vector<ListItemPtr> getAllItems() override
{
// For file system, don't return all files (too many)
// Instead return an empty list - files only show up on search
return {};
}
QString pluginName() const override
{
return "File System";
}
QString pluginDescription() const override
{
return "Searches files in the home directory";
}
int priority() const override
{
return 50; // Lower priority than applications
}
private:
std::string m_searchPath;
};
}