Toggle sort text lines in clipboard Aa-Za ⇄ Zz-Aa

There is probably a more Maestronic way of doing this, but FWIW here is a macro which sorts text lines in the clipboard (case-insensitive). If you fire it again it sorts Z-A, toggling each time.

Toggle-sort clipboard contents (AZ ⇄ ZA).kmmacros (19.0 KB)

(function () {
    'use strict';

    var fnAaZz = function (a, b) {
            return a.toLowerCase()
                .localeCompare(
                    b.toLowerCase()
                );
        },
        fnZzAa = function (a, b) {
            return b.toLowerCase()
                .localeCompare(
                    a.toLowerCase()
                );
        };

    var a = Application.currentApplication(),
        sa = (a.includeStandardAdditions = true, a),
        strClip = sa.theClipboard(),
        lstLines = (
            strClip && typeof strClip === 'string'
        ) ? strClip.split(/[\n\r]+/) : [],
        strLines = lstLines.length ? lstLines.join('\n') : undefined,
        strSorted = strLines ? lstLines.sort(fnAaZz).join('\n') : undefined;

    return strSorted ? (function () {
        // Toggle AZ <> ZA if already sorted
        var blnZA = (strLines === strSorted),
            strOut = blnZA ? lstLines.sort(fnZzAa).join('\n') : strSorted;

        sa.setTheClipboardTo(
            strOut
        );

        sa.displayNotification(strOut, {
            withTitle: "Clipboard sorted    " + (blnZA ? "Z-A" : "A-Z")
        });

        return strOut;
    })() : '';
})();
2 Likes

Rob, thanks for this macro and JXA code.

If I have a list (array) of text already sorted, and I want to add a new element at the proper sort location, what would be the best way to do that?

Array.sort() will handle that efficiently.

lstSorted.concat(x).sort()
[1, 2, 3, 4, 5].concat(3.5).sort()

// --> [1, 2, 3, 3.5, 4, 5]
1 Like

Thanks, Rob.

I had done quite a bit of research, and did not see a clear “winner”.
A lot of posters at stackoverflow don’t seem to like the Array.sort() function.
But maybe that is for large, long lists.

Your approach seems to work well for me.

On the typical scales of the scripting context (smallish numbers of users per coder, and modest-sized datasets), coder time is much more scarce and valuable than run-time.

(Browser competition means that JavaScript engines are pretty heavily optimised, well beyond what casual scripting really needs, on the whole)

1 Like

As a footnote to the general mechanics sorting with Execute JavaScript for Automation actions, I have put some basic examples at:

http://rosettacode.org/wiki/Sort_using_a_custom_comparator#ES6

The examples are in ES6 JS, if you are running a pre-Sierra system you can obtain the ES5 equivalent by pasting the ES6 code into the left-hand panel at:

I’ve added ES5 source there now, and also included, with a couple of examples a mappendOrdering function, which makes it easier to write secondary (+) sorts like ‘by descending length and then AZ’

http://rosettacode.org/wiki/Sort_using_a_custom_comparator#JavaScript

Can someone kindly share with me a version of the script that doesn't have the sort toggle option? It's driving me nuts! I just need it to sort in ascending order.

TIA!

Kick these tires:

Toggle-sort clipboard contents (AZ only).kmmacros (18.7 KB)

2 Likes

Works great. I tried modifying it myself but my JavaScript knowledge is close to nil.

Thank you so much!

You're welcome!

1 Like

These days, FWIW, | would probably use an option type to define what happens if the clipboard contents isn't textual.

either(msg => msg)(copyText)(
    bindLR(clipTextLR())(
        compose(
            Right,
            unlines,
            sortBy(
                a => b => a.localeCompare(b)
            ),
            lines
        )
    )

Sort text in clipboard (AZ).kmmacros (21.0 KB)

Full JS source
(() => {
    'use strict';

    // Rob Trew 2020

    ObjC.import('AppKit');

    const main = () =>
        either(msg => msg)(copyText)(
            bindLR(clipTextLR())(
                compose(
                    Right,
                    unlines,
                    sortBy(
                        a => b => a.localeCompare(b)
                    ),
                    lines
                )
            )
        );


    //  ----------------------- JXA -----------------------

    // clipTextLR :: () -> Either String String
    const clipTextLR = () => {
        const
            v = ObjC.unwrap($.NSPasteboard.generalPasteboard
                .stringForType($.NSPasteboardTypeString));
        return Boolean(v) && v.length > 0 ? (
            Right(v)
        ) : Left('No utf8-plain-text found in clipboard.');
    };

    // copyText :: String -> IO String
    const copyText = s => {
        // String copied to general pasteboard.
        const pb = $.NSPasteboard.generalPasteboard;
        return (
            pb.clearContents,
            pb.setStringForType(
                $(s),
                $.NSPasteboardTypeString
            ),
            s
        );
    };


    // --------------------- GENERIC ----------------------
    //  https://github.com/RobTrew/prelude-jxa

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });


    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });


    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => undefined !== m.Left ? (
            m
        ) : mf(m.Right);


    // compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
    const compose = (...fs) =>
        fs.reduce(
            (f, g) => x => f(g(x)),
            x => x
        );


    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
        fr => e => 'Either' === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;


    // lines :: String -> [String]
    const lines = s =>
        // A list of strings derived from a single
        // newline-delimited string.
        0 < s.length ? (
            s.split(/[\r\n]/)
        ) : [];


    // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
    const sortBy = f =>
        xs => xs.slice()
        .sort((a, b) => f(a)(b));


    // unlines :: [String] -> String
    const unlines = xs =>
        // A single string formed by the intercalation
        // of a list of strings with the newline character.
        xs.join('\n');

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