Change case of selected text (Initial Caps -> Upper -> Lower -> Initial Caps)

A case-toggler (case 'cycler' really):

  • Mixed case -> Upper
  • Upper case -> Lower
  • Lower -> Initial Caps

Change case of selected text (Title -> Upper -> Lower -> Title).kmmacros (21.7 KB)
updated

JS source:

(() => {
    'use strict';

    // main :: () -> IO String
    const main = () => {
        const
            sa = standardAdditions(),
            clip = sa.theClipboard(),
            strReCased = typeof clip !== 'object' ? (
                caseToggled(
                    sa.theClipboard()
                )
            ) : '';
        return strReCased.length > 0 ? (
            sa.setTheClipboardTo(
                strReCased
            ),
            strReCased
        ) : '';
    };

    // caseToggled :: String -> String
    const caseToggled = s => {
        const cs = chars(s);
        return !any(isUpper, cs) ? (
            toTitle(s)
        ) : !any(isLower, cs) ? (
            toLower(s)
        ) : toUpper(s);
    };

    // GENERIC FUNCTIONS --------------------------------------

    // | True if any contained element satisfies the predicate.
    // any :: (a -> Bool) -> [a] -> Bool
    const any = (p, xs) => xs.some(p);

    // chars :: String -> [Char]
    const chars = s => s.split('');

    // filter :: (a -> Bool) -> [a] -> [a]
    const filter = (f, xs) => xs.filter(f);

    // isLower :: Char -> Bool
    const isLower = c =>
        /[a-z]/.test(c);

    // isUpper :: Char -> Bool
    const isUpper = c =>
        /[A-Z]/.test(c);

    // map :: (a -> b) -> [a] -> [b]
    const map = (f, xs) => xs.map(f);

    // regexMatches :: String -> String -> [[String]]
    const regexMatches = (strRgx, strHay) => {
        const rgx = new RegExp(strRgx, 'g');
        let m = rgx.exec(strHay),
            xs = [];
        while (m)(xs.push(m), m = rgx.exec(strHay));
        return xs;
    };

    // toLower :: String -> String
    const toLower = s => s.toLowerCase();

    // toTitle :: String -> String
    const toTitle = s =>
        regexMatches(/(\w)(\w+)(\b[\W]*|$)/g, s)
        .map(ms => ms[1].toUpperCase() + ms[2].toLowerCase() + ms[3])
        .join('');

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

    // JXA ---

    // standardAdditions :: () -> Application
    const standardAdditions = () =>
        Object.assign(Application.currentApplication(), {
            includeStandardAdditions: true
        });

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

5 Likes

Great to see the code for doing this. Also, note, that a menu bar utility called PopClip can also do this without using a keyboard shortcut. But it’s always great to have alternative methods.

Love the macro. Any way to maintain formatting in MS word? I'm using 2011.

These are generic plain-text manipulations, so the rich text wrapping gets discarded.

It should be possible, if you are familiar with the VBA interface, to write something specific to MSWord which conserves the formatting.

1 Like

I have just remembered – in MS Word there is a built-in ⇧F3 application of wdNextCase to the selected text.

I think that should do what you need.

1 Like

I love this macro! The ⇧F13 shortcut didn't work for me in MSFT Word 2011. Therefore, this macro solves the issue for me. Thank you!