Files
noctalia-shell/Services/UI/SettingsSearchService.qml
T

159 lines
3.9 KiB
QML

pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services.Compositor
import qs.Services.Power
import qs.Services.System
Singleton {
id: root
property var searchIndex: []
FileView {
path: Quickshell.shellDir + "/Assets/settings-search-index.json"
watchChanges: false
printErrors: false
onLoaded: {
try {
root.searchIndex = JSON.parse(text());
} catch (e) {
root.searchIndex = [];
}
}
}
readonly property var _roots: ({
"CompositorService": CompositorService,
"Settings": Settings,
"Quickshell": Quickshell,
"IdleService": IdleService,
"SystemStatService": SystemStatService,
"SoundService": SoundService
})
function isEntryVisible(entry) {
if (!entry.visibleWhen || entry.visibleWhen.length === 0)
return true;
for (let i = 0; i < entry.visibleWhen.length; i++) {
if (!_evalCondition(entry.visibleWhen[i]))
return false;
}
return true;
}
function _resolveValue(path) {
const parts = path.split(".");
const rootObj = _roots[parts[0]];
if (rootObj === undefined)
return undefined;
let obj = rootObj;
for (let i = 1; i < parts.length; i++) {
if (obj === undefined || obj === null)
return undefined;
let key = parts[i];
if (key.endsWith("?"))
key = key.slice(0, -1);
obj = obj[key];
}
return obj;
}
function _splitAnd(expr) {
const parts = [];
let depth = 0;
let current = "";
for (let i = 0; i < expr.length; i++) {
const ch = expr[i];
if (ch === "(")
depth++;
else if (ch === ")")
depth--;
else if (depth === 0 && ch === "&" && i + 1 < expr.length && expr[i + 1] === "&") {
parts.push(current);
current = "";
i++;
continue;
}
current += ch;
}
parts.push(current);
return parts;
}
function _evalCondition(expr) {
expr = expr.trim();
// Strip outer parentheses
if (expr.startsWith("(") && expr.endsWith(")")) {
let depth = 0;
let allWrapped = true;
for (let i = 0; i < expr.length - 1; i++) {
if (expr[i] === "(")
depth++;
else if (expr[i] === ")")
depth--;
if (depth === 0) {
allWrapped = false;
break;
}
}
if (allWrapped)
return _evalCondition(expr.slice(1, -1));
}
// AND: all parts must be true
if (expr.includes("&&")) {
const parts = _splitAnd(expr);
if (parts.length > 1) {
for (let i = 0; i < parts.length; i++) {
if (!_evalCondition(parts[i]))
return false;
}
return true;
}
}
// Negation
if (expr.startsWith("!"))
return !_evalCondition(expr.slice(1).trim());
// Literal false
if (expr === "false")
return false;
// Strip nullish coalescing fallback
const nullishMatch = expr.match(/^(.+?)\s*\?\?\s*(?:false|true)\s*$/);
if (nullishMatch)
return _evalCondition(nullishMatch[1]);
// === comparison
let m = expr.match(/^(.+?)\s*===\s*"([^"]*)"\s*$/);
if (m)
return _resolveValue(m[1].trim()) === m[2];
// !== comparison
m = expr.match(/^(.+?)\s*!==\s*"([^"]*)"\s*$/);
if (m)
return _resolveValue(m[1].trim()) !== m[2];
// > comparison
m = expr.match(/^(.+?)\s*>\s*(\d+)\s*$/);
if (m)
return _resolveValue(m[1].trim()) > parseInt(m[2]);
// Simple property path — resolve and return truthiness
const val = _resolveValue(expr);
if (val !== undefined)
return !!val;
// Unrecognized expression — assume visible
return true;
}
}