158 lines
3.9 KiB
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;
|
|
}
|
|
}
|
|
}
|