Action

Renumber lists

Last update almost 6 years ago

(Re)number (selected, or all) Markdown and Taskpaper ordered lists.

HTML display automatically corrects numeric disorder in MD etc lists,
but this action aims to fix disordered numbering in the plain text document too.

Steps

  • script

    // GENERIC FUNCTIONS ---------------------------------------
    
    // concatMap :: (a -> [b]) -> [a] -> [b]
    const concatMap = (f, xs) => [].concat.apply([], xs.map(f));
    
    // Typical usage: groupBy(on(eq, f), xs)
    // groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
    const groupBy = (f, xs) => {
        const dct = xs.slice(1)
            .reduce((a, x) => {
                const h = a.active.length > 0 ? (
                    a.active[0]
                ) : undefined;
                return h !== undefined && f(h, x) ? {
                    active: a.active.concat([x]),
                    sofar: a.sofar
                } : {
                    active: [x],
                    sofar: a.sofar.concat([a.active])
                };
            }, {
                active: xs.length > 0 ? [xs[0]] : [],
                sofar: []
            });
        return dct.sofar.concat(dct.active.length > 0 ? (
            [dct.active]
        ) : []);
    };
    
    // justifyRight :: Int -> Char -> String -> String
    const justifyRight = (n, cFiller, strText) =>
        n > strText.length ? (
            (cFiller.repeat(n) + strText)
            .slice(-n)
        ) : strText;
    
    // length :: [a] -> Int
    const length = xs => xs.length;
    
    // lines :: String -> [String]
    const lines = s => s.split(/[\r\n]/);
    
    // map :: (a -> b) -> [a] -> [b]
    const map = (f, xs) => xs.map(f);
    
    //Ordering: (LT|EQ|GT):
    //  GT: 1 (or other positive n)
    //	EQ: 0
    //  LT: -1 (or other negative n)
    // minimumBy :: (a -> a -> Ordering) -> [a] -> a
    const minimumBy = (f, xs) =>
        xs.reduce((a, x) => a === undefined ? x : (
            f(x, a) < 0 ? x : a
        ), undefined);
    
    // showLog :: a -> Console String
    const showLog = (...args) =>
        alert(
            args.map(JSON.stringify)
            .join(' -> ')
        );
    
    // unlines :: [String] -> String
    const unlines = xs => xs.join('\n');
    
    // APPLYING ANY FUNCTION (of type [String] -> [String])
    // TO SELECTED (OR ALL) LINES IN DRAFT
    
    // draftOrLinesUpdated :: IO () ->
    //                    ([String] -> [String]) -> IO ()
    const draftOrLinesUpdated = f => {
        const
            functionName = f.name, // Used for logging.
            // Selected lines or whole of draft.
            [intStart, intLength] = editor.getSelectedLineRange(),
            [intFrom, intChars] = (
                editor.getSelectedText()
                .length > 0 ? (
                    [intStart, intLength]
                ) : [0, editor.getText().length]
            ),
            // Concatenated result of applying f to the list of lines
            strUpdated = unlines(f(lines(
                editor.getTextInRange(intFrom, intChars)
            )));
        return (
            // Various channels, for testing and use.
            editor.setTextInRange(intFrom, intChars, strUpdated),
            console.log(functionName, strUpdated),
            strUpdated
        );
    };
    
  • script

    (() => {
        'use strict';
    
        return draftOrLinesUpdated(
            draftLines => {
                const
                    rgxPrefix = /^\s*\d+\.\s*/,
                    rgxOLText = /^\s*\d+\.\s*(.*)$/,
                    rgxIndent = /^(\s*)\d+\./,
    
                    // renumbered :: [ {isOL :: Bool,
                    //                  txt :: String} ] -> [String]
                    renumbered = xs => {
                        const
                            // Longest sequence of digits,
                            intDigits = (xs.length - 1)
                            .toString()
                            .length,
    
                            // and shortest indent string.
                            indent = minimumBy(
                                length,
                                map(
                                    x => rgxIndent.exec(x.txt)[1],
                                    xs
                                )
                            );
                        return map(
                            (dict, i) =>
                            indent + (1 + i).toString() + '. ' +
                            rgxOLText.exec(dict.txt)[1],
                            xs
                        );
                    };
    
                // The list of line groups is flattened by concatMap
                // and returned as a list of strings.
                return concatMap(
                    xs => ((xs.length > 0) && xs[0].isOL) ? (
                        renumbered(xs)
                    ) : map(x => x.txt, xs),
                    groupBy(
                        // Adjacent lines grouped by whether
                        // they are numbered.
                        (a, b) => a.isOL === b.isOL,
                        map(line => ({
                                isOL: rgxPrefix.test(line),
                                txt: line
                            }),
                            draftLines
                        )
                    )
                );
            }
        );
    })();
    

Options

  • After Success Default
    Notification Info
    Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.