Watch
1
0
Fork
You've already forked souveraine
0
souveraine/surfaces/quickshell/ii-base/services/FileSearch.qml

158 lines
3.9 KiB
QML

pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import qs.modules.common
import qs.modules.common.functions
Singleton {
id: root
property var results: []
property string pendingQuery: ""
property string activeQuery: ""
property int searchId: 0
property bool running: false
function normalizePath(path) {
if (!path) return "";
let normalized = FileUtils.trimFileProtocol(String(path)).trim();
if (normalized.startsWith("~/")) {
normalized = FileUtils.trimFileProtocol(Directories.home) + normalized.slice(1);
}
if (normalized.endsWith("/")) {
normalized = normalized.slice(0, -1);
}
return normalized;
}
function isExcluded(path) {
if (Config.options.search.fileSearch.excludeHiddenDirs) {
// Match any hidden dir segment: "/.name/" or ending "/.name"
if (path.match(/\/\.[^/]+(\/|$)/)) {
return true;
}
}
const exclude = Config.options.search.fileSearch.exclude;
if (!exclude || exclude.length === 0) return false;
for (let i = 0; i < exclude.length; i++) {
const needle = String(exclude[i]).trim();
if (needle.length === 0) continue;
if (path.includes(`/${needle}/`) || path.endsWith(`/${needle}`)) {
return true;
}
}
return false;
}
function isAllowedByPath(path) {
const paths = Config.options.search.fileSearch.paths;
if (!paths || paths.length === 0) return true;
for (let i = 0; i < paths.length; i++) {
const base = normalizePath(paths[i]);
if (!base) continue;
if (path === base || path.startsWith(base + "/")) {
return true;
}
}
return false;
}
function shouldSearch(query) {
if (!Config.options.search.fileSearch.enable) return false;
if (!query || query.trim().length < 2) return false;
return true;
}
function reset() {
results = [];
activeQuery = "";
if (plocateProc.running) {
plocateProc.running = false;
}
}
function search(query) {
pendingQuery = String(query || "").trim();
if (!shouldSearch(pendingQuery)) {
reset();
return;
}
debounceTimer.restart();
}
Timer {
id: debounceTimer
interval: 200
repeat: false
onTriggered: {
root.runSearch(pendingQuery);
}
}
function runSearch(query) {
const trimmed = String(query || "").trim();
if (!shouldSearch(trimmed)) { reset(); return; }
if (plocateProc.running) plocateProc.running = false;
searchId += 1;
activeQuery = trimmed;
results = [];
const maxResults = Config.options.search.fileSearch.maxResults;
// Tag each line with d: or f: prefix
const command = [
"bash", "-c",
`plocate -i --basename --limit ${maxResults} '${trimmed}' | while IFS= read -r p; do [ -d "$p" ] && printf 'd:%s\n' "$p" || printf 'f:%s\n' "$p"; done`
];
plocateProc.runId = searchId;
plocateProc.accepted = 0;
plocateProc.command = command;
plocateProc.running = true;
root.running = true;
}
Process {
id: plocateProc
property int runId: 0
property int accepted: 0
stdout: SplitParser {
onRead: line => {
if (plocateProc.runId !== root.searchId) return;
const raw = String(line || "").trim();
if (!raw || raw.length < 3) return;
const isDir = raw.startsWith("d:");
const rawPath = raw.slice(2);
const normalized = root.normalizePath(rawPath);
if (!normalized) return;
if (!root.isAllowedByPath(normalized)) return;
if (root.isExcluded(normalized)) return;
if (plocateProc.accepted >= Config.options.search.fileSearch.maxResults) {
plocateProc.running = false;
return;
}
plocateProc.accepted += 1;
root.results = root.results.concat([{ path: normalized, isDir: isDir }]);
}
}
stderr: SplitParser {
onRead: line => {
if (plocateProc.runId !== root.searchId) return;
}
}
onExited: (exitCode, exitStatus) => {
if (plocateProc.runId !== root.searchId) return;
root.running = false;
}
}
}