Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

quickshell/nuguland/launch.js

Raw
// launch.js — pure app-launcher logic: normalize, fuzzy rank, desktop-entry mapping.
// NO I/O. Dual-mode: node `require` (tests) + QML import.

var GLYPH_APP = "\uF135"; // rocket fallback when themed icon misses

function normalize(text) {
    return String(text == null ? "" : text).trim().toLowerCase();
}

function prefixIntent(raw) {
    return { intent: "app", query: String(raw == null ? "" : raw).trim() };
}

function isWordChar(ch) {
    return /[a-z0-9]/.test(ch);
}

function subsequenceMatch(q, t) {
    var qi = 0;
    var positions = [];
    for (var ti = 0; ti < t.length && qi < q.length; ti++) {
        if (t.charAt(ti) === q.charAt(qi)) {
            positions.push(ti);
            qi++;
        }
    }
    if (qi < q.length)
        return { matched: false, firstIndex: -1, contiguous: 0, boundaryHits: 0 };

    var contiguous = 0;
    var boundaryHits = 0;
    for (var i = 0; i < positions.length; i++) {
        var p = positions[i];
        if (i > 0 && positions[i] === positions[i - 1] + 1)
            contiguous++;
        if (p === 0 || !isWordChar(t.charAt(p - 1)))
            boundaryHits++;
    }
    return { matched: true, firstIndex: positions[0], contiguous: contiguous, boundaryHits: boundaryHits };
}

function fuzzyScore(query, title, boost) {
    var b = Number(boost) || 0;
    var q = normalize(query);
    var t = normalize(title);
    if (q.length === 0)
        return b;
    if (t === q)
        return b + 1000;
    if (t.indexOf(q) === 0)
        return b + 800 - t.length;

    var m = subsequenceMatch(q, t);
    if (m.matched) {
        var allBoundary = m.boundaryHits === q.length;
        var base = allBoundary ? 600 : 350;
        return b + base + m.contiguous * 10 + m.boundaryHits * 15 - m.firstIndex;
    }
    return -1;
}

function list(value) {
    var out = [];
    var src = value || [];
    for (var i = 0; i < src.length; i++) {
        var text = String(src[i] || "").trim();
        if (text.length > 0)
            out.push(text);
    }
    return out;
}

function commandList(value) {
    var out = [];
    var src = value || [];
    for (var i = 0; i < src.length; i++)
        out.push(String(src[i]));
    return out;
}

function primaryCategory(categories) {
    var skip = { Qt: true, KDE: true, GNOME: true, GTK: true, Application: true, Core: true };
    for (var i = 0; i < categories.length; i++) {
        var cat = categories[i];
        if (!skip[cat])
            return cat;
    }
    return categories[0] || "App";
}

function buildDesktopActions(actions) {
    var rows = [];
    var src = actions || [];
    for (var i = 0; i < src.length; i++) {
        var a = src[i];
        if (!a || !a.name)
            continue;
        rows.push({
            id: String(a.id || a.name),
            title: String(a.name),
            icon: String(a.icon || ""),
            exec: String(a.execString || ""),
            execArgv: commandList(a.command)
        });
    }
    return rows;
}

function buildApps(entries) {
    var rows = [];
    var src = entries || [];
    for (var i = 0; i < src.length; i++) {
        var e = src[i];
        if (!e || !e.name)
            continue;

        var id = String(e.id || e.name);
        var categories = list(e.categories);
        var keywords = list(e.keywords);
        var genericName = String(e.genericName || "");
        var comment = String(e.comment || "");
        var kind = genericName || primaryCategory(categories);
        var category = primaryCategory(categories);
        var actions = buildDesktopActions(e.actions);

        rows.push({
            id: "app:" + id,
            title: String(e.name),
            subtitle: kind + (category && category !== kind ? " · " + category : ""),
            genericName: genericName,
            comment: comment,
            desktopId: id,
            desktopPath: id + ".desktop",
            startupClass: String(e.startupClass || ""),
            exec: String(e.execString || ""),
            execArgv: commandList(e.command),
            workingDirectory: String(e.workingDirectory || ""),
            icon: String(e.icon || ""),
            terminal: !!e.runInTerminal,
            categories: categories,
            keywords: keywords,
            actions: actions,
            details: [
                { label: "category", value: category },
                { label: "keywords", value: keywords.slice(0, 4).join("; ") || "—" },
                { label: "terminal", value: e.runInTerminal ? "yes" : "no" }
            ],
            searchText: [e.name, genericName, comment, id, e.startupClass].concat(categories).concat(keywords).join(" "),
            route: "desktop-entry"
        });
    }
    rows.sort(function (a, b) {
        return String(a.title).localeCompare(String(b.title));
    });
    return rows;
}

function shapeRow(row, provider, score) {
    var next = Object.assign({}, row || {});
    next.provider = "app";
    next.iconRole = "desktop-entry";
    next.verb = "open";
    next.glyph = GLYPH_APP;
    next.score = score;
    next.keyboard = true;
    return next;
}

function rankResults(rawQuery, sources) {
    var apps = (sources || {}).apps || [];
    var intent = prefixIntent(rawQuery);
    var query = intent.query;
    var results = [];

    for (var i = 0; i < apps.length; i++) {
        var app = apps[i];
        var score = Math.max(
            fuzzyScore(query, app.title, 900),
            fuzzyScore(query, app.searchText || app.title, 760)
        );
        if (score >= 0)
            results.push(shapeRow(app, "app", score));
    }

    results.sort(function (x, y) {
        return y.score - x.score || String(x.title).localeCompare(String(y.title));
    });

    return {
        results: results,
        selectedIndex: results.length > 0 ? 0 : -1,
        providerIntent: intent.intent
    };
}

if (typeof module !== 'undefined' && module.exports) {
    module.exports = {
        normalize: normalize,
        prefixIntent: prefixIntent,
        subsequenceMatch: subsequenceMatch,
        fuzzyScore: fuzzyScore,
        buildDesktopActions: buildDesktopActions,
        buildApps: buildApps,
        shapeRow: shapeRow,
        rankResults: rankResults
    };
}