Something went wrong. Try again.
The agentic engineering control plane for the posthuman future
Something went wrong. Try again.
9.2 kB · 284 lines
C++
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285#include "FuzzyPathRows.h"
#include <QDir>#include <QDirIterator>#include <QFileInfo>#include <QFileInfoList>#include <QSet>#include <QVariantMap>
#include <algorithm>
#include "FuzzyMatch.h"
namespace {
// The directories the file walk never descends into.[[nodiscard]] const QSet<QString>& heavyDirectories(){ static const QSet<QString> dirs = { QStringLiteral(".git"), QStringLiteral(".jj"), QStringLiteral(".hg"), QStringLiteral("node_modules"), QStringLiteral(".venv"), QStringLiteral(".cache"), QStringLiteral("build"), QStringLiteral("result"), QStringLiteral(".direnv")}; return dirs;}
// Expand a leading `~`, make the path absolute, and clean it.[[nodiscard]] QString resolvePath(const QString& path){ QString value = path; if (value.startsWith(QLatin1Char('~'))) { value.replace(0, 1, QDir::homePath()); } if (!QFileInfo(value).isAbsolute()) { value = QDir::currentPath() + QLatin1Char('/') + value; } return QDir::cleanPath(value);}
// The walk root. An empty root means the home directory.[[nodiscard]] QString resolveRoot(const QString& root){ if (root.isEmpty()) { return QDir::homePath(); } return resolvePath(root);}
// The path the row shows: relative to `base` when the entry lies under it,// absolute otherwise. This is also the string the scorer ranks, because a long// absolute prefix would sink every match.[[nodiscard]] QString displayPath(const QString& absolute, const QString& base){ QString relative = QDir(base).relativeFilePath(absolute); if (relative == QLatin1String("..") || relative.startsWith(QLatin1String("../"))) { return absolute; } return relative;}
// One entry row: the name, the displayed path, the kind, and the absolute// path. `kind` is empty for the Entries scope, which decides the kind and the// subtitle per entry.[[nodiscard]] QVariantMap entryRow(const QString& absolute, const QString& base, const QString& kind){ const QFileInfo info(absolute); const bool directory = info.isDir(); QVariantMap row; row.insert(QStringLiteral("title"), info.fileName()); if (kind.isEmpty()) { // The Entries scope: the field already shows the directory in full, so a // file's subtitle would repeat it. A directory says what it is instead, and // that is the one thing the row cannot show by itself. row.insert(QStringLiteral("subtitle"), directory ? QStringLiteral("directory") : QString()); row.insert(QStringLiteral("kind"), directory ? QStringLiteral("directory") : QStringLiteral("file")); } else { row.insert(QStringLiteral("subtitle"), displayPath(absolute, base)); row.insert(QStringLiteral("kind"), kind); } row.insert(QStringLiteral("path"), absolute); return row;}
// The terminal row. Its subtitle is the directory the terminal spawns in.[[nodiscard]] QVariantMap terminalRow(const QString& base){ QVariantMap row; row.insert(QStringLiteral("title"), QStringLiteral("Terminal")); row.insert(QStringLiteral("subtitle"), base); row.insert(QStringLiteral("kind"), QStringLiteral("terminal")); row.insert(QStringLiteral("path"), QString()); return row;}
// The immediate child directories of `base`, case-insensitive name order.[[nodiscard]] WalkResult walkDirectories(const QString& base){ const QFileInfoList entries = QDir(base).entryInfoList( QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable, QDir::Name | QDir::IgnoreCase); WalkResult result; for (const QFileInfo& info : entries) { if (result.paths.size() >= kMaxWalkPaths) { result.limit = WalkLimit::Count; break; } result.paths.append(info.absoluteFilePath()); } return result;}
// The immediate child directories and files of `base`, directories first and// names case-insensitive inside each group.[[nodiscard]] WalkResult walkEntries(const QString& base){ WalkResult result; const QFileInfoList entries = QDir(base).entryInfoList( QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Readable, QDir::DirsFirst | QDir::Name | QDir::IgnoreCase); for (const QFileInfo& info : entries) { if (result.paths.size() >= kMaxWalkPaths) { result.limit = WalkLimit::Count; break; } result.paths.append(info.absoluteFilePath()); } return result;}
// Descend `directory` for files. The heavy directories are tested per name and// pruned before the descent, so their contents are never read; the depth bound// stops the descent, which a QDirIterator cannot do.void collectFiles(const QString& directory, int depth, WalkResult& result){ if (depth > kMaxWalkDepth) { result.limit = WalkLimit::Depth; return; } const QFileInfoList entries = QDir(directory).entryInfoList( QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden, QDir::Name | QDir::IgnoreCase); QStringList subdirectories; for (const QFileInfo& info : entries) { if (result.paths.size() >= kMaxWalkPaths) { result.limit = WalkLimit::Count; return; } if (info.isDir()) { // A hidden directory is still descended, so .github/workflows/ci.yml is // reachable; only its own listing stays hidden. if (!heavyDirectories().contains(info.fileName())) { subdirectories.append(info.absoluteFilePath()); } continue; } if (info.fileName().startsWith(QLatin1Char('.'))) { // The name test, not QFileInfo::isHidden(): this listing asks for hidden // entries so that hidden directories can be descended, and a dotfile is // what "hidden" means on the platforms this app runs on. continue; } result.paths.append(info.absoluteFilePath()); } for (const QString& subdirectory : subdirectories) { collectFiles(subdirectory, depth + 1, result); }}
[[nodiscard]] WalkResult walkFiles(const QString& base){ WalkResult result; collectFiles(base, 1, result); return result;}
} // namespace
WalkResult walkPaths(const QString& root, PathScope scope){ const QString base = resolveRoot(root); switch (scope) { case PathScope::Directories: return walkDirectories(base); case PathScope::Entries: return walkEntries(base); case PathScope::Files: break; } return walkFiles(base);}
QVariantList rankRows(const QStringList& paths, const QString& root, const QString& query, PathScope scope, bool includeTerminalRow){ const QString base = resolveRoot(root); const bool directories = scope == PathScope::Directories; const bool entries = scope == PathScope::Entries; const bool hasQuery = !query.isEmpty(); // The Entries scope decides each row's kind on its own, so it passes an empty // kind down to entryRow(). QString kind = QStringLiteral("file"); if (directories) { kind = QStringLiteral("directory"); } else if (entries) { kind.clear(); }
// The walk entries filtered and ranked when a query is present; the walk // order otherwise. QStringList candidates = paths; if (hasQuery) { struct Ranked { QString path; int score = 0; }; QList<Ranked> ranked; for (const QString& path : paths) { // The best of the name and the displayed path, so a deep file still // matches when the user types its name. A long path prefix would // otherwise sink every match under the leading-skip penalty. const int score = std::max(fuzzyScore(QFileInfo(path).fileName(), query), fuzzyScore(displayPath(path, base), query)); if (score >= 0) { ranked.append(Ranked{.path = path, .score = score}); } } std::ranges::sort(ranked, [](const Ranked& left, const Ranked& right) { if (left.score != right.score) { return left.score > right.score; } if (left.path.size() != right.path.size()) { return left.path.size() < right.path.size(); } return left.path < right.path; }); candidates.clear(); for (const Ranked& item : ranked) { candidates.append(item.path); } }
QVariantList out; qsizetype count = 0; // A non-empty query that names an existing file contributes that file's row // first, whatever the walk holds. Only the deep walk needs this: the Entries // scope lists that path's directory already. if (scope == PathScope::Files && hasQuery) { QString named = query; if (named.startsWith(QLatin1Char('~'))) { named.replace(0, 1, QDir::homePath()); } if (!QFileInfo(named).isAbsolute()) { named = base + QLatin1Char('/') + named; } named = QDir::cleanPath(named); if (QFileInfo(named).isFile()) { out.append(entryRow(named, base, kind)); ++count; candidates.removeAll(named); } } for (const QString& path : candidates) { if (count >= kMaxRankRows) { break; } out.append(entryRow(path, base, kind)); ++count; }
if (!directories && includeTerminalRow && (!hasQuery || fuzzyScore(QStringLiteral("Terminal"), query) >= 0)) { out.prepend(terminalRow(base)); } return out;}