Extend text selection one word left or right?


#1

Before I reinvent the wheel, are there already actions (or keyboardless tap tricks) for extending a text selection one word to left or right ?


#2

I believe I saw one in the drafts 4 directory… I’d start there!
update:
yes … I just checked the Drafts 4 directory there is an action for moving ‘forward’ and one for ‘backward’

move cursor to right oneword

there is also a ‘select word’ key action:
Select Word

one or a combination of both should be a good starting point for you …


#3

Thanks ! That’s helpful.

Here, FWIW, are a pair of buttons combining those two functions into Left and Right additional selections – (In the end, I needed to write the underlying function to update something else)

Extend selection one more word to the left
http://actions.getdrafts.com/a/1Ie

Extend selection one more word to the right
http://actions.getdrafts.com/a/1Id

The source code is shared – they differ only by a Left ⇄ Right option:

(() => {
    'use strict';

    // main :: IO ()
    const main = () =>
        expandSelnByWord(true, true);


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

    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = (a, b) => ({
        type: 'Tuple',
        '0': a,
        '1': b,
        length: 2
    });

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

    // splitAt :: Int -> [a] -> ([a],[a])
    const splitAt = (n, xs) => Tuple(xs.slice(0, n), xs.slice(n));


    // SELECTION EXTENSIONS  ------------------------------

    // expandSelnByWord :: () -> IO ()
    const expandSelnByWord = (blnMultiWord, blnLeft) => {
        const
            e = editor,
            tplSeln = e.getSelectedRange(),
            tplLine = e.getSelectedLineRange(),
            strLine = e.getTextInRange(...tplLine),
            intPosn = tplSeln[0],
            xy = splitAt(
                intPosn - tplLine[0],
                strLine
            ),
            [dl, dr] = concatMap(
                x => x !== null ? (
                    [x[0].length]
                ) : [0], //
                [/\b[\S]*$/.exec(xy[0]), /^[\S]*\b/.exec(xy[1])]
            );
        return (tplSeln[1] === 0 || dl > 0 && dr > 0) ? (
            e.setSelectedRange(intPosn - dl, dl + dr),
            'extended'
        ) : blnMultiWord ? (
            additionalWord(
                blnLeft, tplSeln, tplLine, strLine
            )
        ) : 'No further';
    };

    // additionalWord :: Bool -> (Int, Int) -> (Int, Int)
    //                                          -> String -> IO ()
    const additionalWord = (blnLeft, tplSeln, tplLine, strLine) => {
        const
            e = editor,
            s = blnLeft ? (
                strLine.slice(0, tplSeln[0] - tplLine[0])
            ) : strLine.slice(tplSeln[0] - tplLine[0] + tplSeln[1]),
            m = blnLeft ? (
                /\b\S+\s*$/.exec(s)
            ) : /^\s*\S+\b/.exec(s),
            d = m ? m[0].length : 1;

        return (
            e.setSelectedRange(
                tplSeln[0] - (blnLeft ? d : 0),
                tplSeln[1] + d
            ),
            m ? 'extending' : (() => {
                const
                    tplNew = e.getSelectedRange(),
                    blnFinished = blnLeft ? (
                        tplNew[0] < 1
                    ) : tplNew[0] + tplNew[1] >= e.getText().length;
                return blnFinished ? (
                    'Finished'
                ) : expandSelnByWord(true, blnLeft);
            })()
        );
    };

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

#4

Thank you! I remembered being the actions I pointed you to but had never seen a meed for them myself… with the selection focus you bring I now see many opportunities to use :grinning: