Three stage outline-folding cycle with one key (Script)

The TaskPaper > Outline menu gives us a rich set of keystrokes for either expanding or collapsing all or part of TaskPaper outlines to some degree.

For some reason (perhaps just laziness) I like to cycle all the things, rather than using one key for collapse and another for expand.

Here is a script, which you can attach to a single key (I happen to use ⌘L) for cycling selected outline items between three states:

Fully collapsed → all children visible → all descendants visible (-> fully collapsed again)

Or, as it is described in the manual of emacs OrgMode, which also uses this pattern:

,-> FOLDED -> CHILDREN -> SUBTREE --.
'-----------------------------------'
https://orgmode.org/guide/Visibility-cycling.html
JavaScript source
(() => {
    'use strict';

    // Outline folding cycle for TaskPaper 3

    // In the style of OrgMode subtree cycling
    // Rotate current subtree among the states:

    // ,-> FOLDED -> CHILDREN -> SUBTREE --.
    // '-----------------------------------'
    // https://orgmode.org/guide/Visibility-cycling.html

    // Rob Trew 2020
    // Ver 0.01

    // main :: IO ()
    const main = () =>
        bindLR(frontDocLR('TaskPaper'))(
            doc => doc.evaluate({
                script: tp3FoldCycle.toString(),
            })
        );

    // tp3FoldCycle :: Editor -> Either String IO String
    const tp3FoldCycle = editor => {
        const
            xs = editor.selection.selectedItems,
            parentDepth = Math.min(
                ...xs.map(x => x.depth)
            ),
            selectedParents = xs.filter(
                x => parentDepth === x.depth && (
                    Boolean(x.bodyString)
                )
            );
        return 0 < selectedParents.length ? (() => {
            const
                foldUp = xs => editor.setCollapsed(xs),
                reveal = xs => editor.setExpanded(xs),
                folded = x => editor.isCollapsed(x),
                sample = selectedParents[0];

            // THREE-STAGE OUTLINE-FOLDING CYCLE
            // FOR SELECTED ITEMS.
            return folded(sample) ? (
                // CHILDREN visible but folded.
                selectedParents.forEach(reveal),
                selectedParents.flatMap(
                    x => x.children
                ).forEach(foldUp),
                '-> Children'
            ) : sample.descendants.some(folded) ? (
                // SUBTREE completely unfolded.
                selectedParents.flatMap(
                    x => x.descendants
                ).forEach(reveal),
                '-> SubTree'
            ) : (
                // FOLDED selections.
                selectedParents.forEach(foldUp),
                '-> Folded'
            );
        })() : 'Nothing selected in TaskPaper.'
    };

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

    // fontDocLR :: IO () -> Either String Doc
    const frontDocLR = appName => {
        // Either the front document of the named app,
        // or an explanatory message if no documents
        // are open, or the application is not running.
        const app = Application(appName);
        return app.running() ? (() => {
            const ds = app.documents;
            return 0 < ds.length ? (
                Right(ds.at(0))
            ) : Left('No documents open in ' + appName);
        })() : Left(appName + ' is not running.');
    };

    // -----------------GENERIC FUNCTIONS------------------
    // 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);

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

Or as a Keyboard Maestro macro:

2 Likes

Thank you. How do I assign the keyboard shortcut?

Are you using Keyboard Maestro ?

(or rather looking for another approach ?)

Essentially the choice is between:

1 Like

FastScripts could be another option to trigger a script from the keyboard. And it is free if you run 10 or less scripts.

I just figured out how to trigger it via Alfred, but there’s a bit of a delay. Is this normal for scripts? Or would it be remedied by running via one of the other options?

I wouldn’t expect a discernible delay - I think you might find the free version of FastScripts faster:

(Your Alfred setup may be compiling it from source each time, whereas FastScripts may be better adapted to using a copy saved from Script Editor with Save As.scpt format )

Thanks for the recommendation. It is noticeably faster when using FastScripts!

1 Like