List Macros With HotkeyTriggers

I was trying to make a simple list of my macros with their hot key triggers - any way to do that?

Thx

@JBG_km - I'll just respond with a suggestion that you may not want to hear. Just use KeyCue. Reason: As you progress, in your Keyboard Maestro journey, your memory for shortcuts will wane. Every day you will be adding to your list. I have over 2300 macros (many of which is for Outlook (job related file system). Some turn on and off based off of usage time) and remembering them all is impossible unless you are a young savant. I use a series of KeyCue, the Trigger by Name macro, Palettes (Conflict, Floating and a Bettertouch Tool activated Palette), Prompts, String triggers and maybe 10 regular Hotkey shortcuts.

All this to say, don't torture yourself. Hot key triggers are great when you have under 50. After that, you are searching. Get KeyCue and be done with it.

Good luck on the many other solutions.

KC

2 Likes

Take a look at Macro Reporter Macro. It displays your macros by group with their enabled status (and the group status) as well as how many times each has been called, what the hot keys are and whether the group is a palette or not. Presumes you have access to your own kitchen sink.

Hey @JBG_km,

Simple? Ha!

You'd think so, but no...

The big question here is – why? Specifically.

  • Do you want a hard copy?
  • Do you want a searchable text file?

See:

Trigger Macro by Name Action

And:

Export Names of all Macros in the Selected Group to CSV or Excel - #9 by ccstone

@mrpasini's macro is nice too.

-Chris

1 Like

A variant on this theme which lists macros:

  • by group,
  • with assigned hotkeys in a left-hand column.

Groups of Macros listed with hotkey column to left.kmmacros (8.6 KB)


Expand disclosure triangle to view JS source
(() => {
    "use strict";

    // Listing of all Keyboard Maestro macros by group
    // Any assigned hotkey(s) listed in column to left.

    // Rob Trew @2024

    // ---------------------- MAIN -----------------------
    const main = () => {
        const
            macros = Application("Keyboard Maestro").macros,

            keyLabels = macros.triggers.description()
                .map(hotkeys),

            keyColWidth = Math.max(
                ...keyLabels.flat().map(x => x.length)
            );

        return groupOnKey(x => x.groupName)(
            sortOn(x => x.groupName)(
                zipWithN(
                    groupName => macroName => triggers => ({
                        groupName, macroName, triggers
                    }),
                    macros.macroGroup.name(),
                    macros.name(),
                    keyLabels
                )
            )
        )
            .map(groupListing(keyColWidth))
            .join("\n")
    };

    // --------------- MACRO GROUP LISTING ---------------

    // groupListing :: Int -> (String, [Macro]) -> String
    const groupListing = keyColWidth =>
        ([groupName, macros]) => [
            `\n${toUpper(groupName)}`,
            ...macros.flatMap(macro => {
                const
                    hs = macro.triggers,
                    n = hs.length,
                    xs = zipWithLong(
                        k => name =>
                            `${k.padEnd(keyColWidth)}\t${name}`
                    )(
                        hs
                    )([
                        macro.macroName
                    ]);

                return 0 < n
                    ? indentedText(xs)
                    : `\t${" ".repeat(keyColWidth)}${indentedText(xs)}`;
            })
        ]
            .join("\n");


    // hotkeys :: [String] -> [String]
    const hotkeys = xs =>
        xs.flatMap(
            k => k.startsWith("The Hot Key")
                ? [keyPart(k)]
                : []
        );


    // keyPart :: String -> String
    const keyPart = s =>
        s.slice(12, -11);


    // indentedText :: [String] -> String
    const indentedText = xs =>
        xs.map(x => `\t${x}`)
            .join("\n");


    // --------------------- GENERIC ---------------------

    // comparing :: Ord a => (b -> a) -> b -> b -> Ordering
    const comparing = f =>
        // The ordering of f(x) and f(y) as a value
        // drawn from {-1, 0, 1}, representing {LT, EQ, GT}.
        x => y => {
            const
                a = f(x),
                b = f(y);

            return a < b
                ? -1
                : a > b
                    ? 1
                    : 0;
        };


    // groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
    const groupBy = eqOp =>
        // A list of lists, each containing only elements
        // equal under the given equality operator, such
        // that the concatenation of these lists is xs.
        xs => 0 < xs.length
            ? (() => {
                const [h, ...t] = xs;
                const [groups, g] = t.reduce(
                    ([gs, a], x) => eqOp(a[0])(x)
                        ? [gs, [...a, x]]
                        : [[...gs, a], [x]],
                    [[], [h]]
                );

                return [...groups, g];
            })()
            : [];


    // groupOnKey :: Eq k => (a -> k) -> [a] -> [(k, [a])]
    const groupOnKey = f =>
        // A list of (k, [a]) tuples, in which each [a]
        // contains only elements for which f returns the
        // same value, and in which k is that value.
        // The concatenation of the [a] in each tuple === xs.
        xs => 0 < xs.length
            ? groupBy(a => b => a[0] === b[0])(
                xs.map(x => [f(x), x])
            )
                .map(gp => [
                    gp[0][0],
                    gp.map(ab => ab[1])
                ])
            : [];


    // maximumBy :: (a -> a -> Ordering) -> [a] -> a
    const maximumBy = f =>
        xs => 0 < xs.length
            ? xs.slice(1).reduce(
                (a, x) => 0 < f(x)(a)
                    ? x
                    : a,
                xs[0]
            )
            : undefined;

    // length :: [a] -> Int
    const length = xs =>
        // Returns Infinity over objects without finite
        // length. This enables zip and zipWith to choose
        // the shorter argument when one is non-finite,
        // like cycle, repeat etc
        "Node" !== xs.type
            ? "GeneratorFunction" !== (
                xs.constructor.constructor.name
            )
                ? xs.length
                : Infinity
            : lengthTree(xs);


    // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
    const sortBy = f =>
        // A copy of xs sorted by the comparator function f.
        xs => xs.slice()
            .sort((a, b) => f(a)(b));


    // sortOn :: Ord b => (a -> b) -> [a] -> [a]
    const sortOn = f =>
        // Equivalent to sortBy(comparing(f)), but with f(x)
        // evaluated only once for each x in xs.
        // ('Schwartzian' decorate-sort-undecorate).
        xs => sortBy(
            comparing(x => x[0])
        )(
            xs.map(x => [f(x), x])
        )
            .map(x => x[1]);


    // toUpper :: String -> String
    const toUpper = s =>
        s.toLocaleUpperCase();


    // zipWithLong :: (a -> a -> a) -> [a] -> [a] -> [a]
    const zipWithLong = f => {
        // A list with the length of the *longer* of
        // xs and ys, defined by zipping with a
        // custom function, rather than with the
        // default tuple constructor.
        // Any unpaired values, where list lengths differ,
        // are simply appended.
        const go = xs =>
            ys => 0 < xs.length
                ? 0 < ys.length
                    ? [f(xs[0])(ys[0])].concat(
                        go(
                            xs.slice(1)
                        )(
                            ys.slice(1)
                        )
                    )
                    : xs
                : ys;

        return go;
    };


    // zipWithN :: (a -> b -> ... -> c) -> ([a], [b] ...) -> [c]
    const zipWithN = (...args) =>
        // Uncurried function of which the first argument is a
        // curried function, and all remaining arguments are lists.
        1 < args.length
            ? (
                ([f, ...xs]) => xs.slice(1).reduce(
                    // apZip
                    (gs, vs) => gs.map((g, i) => g(vs[i])),
                    xs[0].map(f)
                )
            )(args)
            : [];

    // MAIN ---
    return main();
})();
1 Like

Just an FYI, I have a "Macro Explorer" macro coming, which I was holding off on releasing before I tweaked a few things, but perhaps I'll post it sooner rather than later. Here's a screenshot:

It's got sorting, filtering, and all sorts of fun stuff. Keep an eye open for it.

9 Likes