Skip to content

Instantly share code, notes, and snippets.

@RobTrew
Last active January 17, 2023 16:45

Revisions

  1. RobTrew revised this gist May 24, 2018. 1 changed file with 16 additions and 4 deletions.
    20 changes: 16 additions & 4 deletions BBDrafts.js
    Original file line number Diff line number Diff line change
    @@ -17,10 +17,14 @@
    // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    // OTHER DEALINGS IN THE SOFTWARE.

    // Ver 0.09
    // Ver 0.10

    const Drafts = () => {
    const bb = Application('BBEdit');
    const
    bb = Application('BBEdit'),
    sa = Object.assign(Application.currentApplication(), {
    includeStandardAdditions: true
    });

    // Left :: a -> Either a b
    const Left = x => ({
    @@ -73,8 +77,12 @@ const Drafts = () => {
    ) : Nothing();

    return {
    app: {
    getClipboard: () => sa.theClipboard(),
    setClipboard: s => sa.setTheClipboardTo(s)
    },
    draft: {

    },
    editor: {
    // Get the full text currently loaded in the editor.
    @@ -200,4 +208,8 @@ const Drafts = () => {
    };
    };

    const editor = Drafts().editor;
    const
    drafts = Drafts(),
    editor = drafts.editor,
    app = drafts.app;

  2. RobTrew revised this gist May 21, 2018. 1 changed file with 7 additions and 5 deletions.
    12 changes: 7 additions & 5 deletions BBDrafts.js
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    // A draft JXA library which aims to provide macOS BBEdit
    // A draft JXA library which aims to provide macOS BBEdit
    // (https://www.barebones.com/products/bbedit/)
    //
    // with some of the iOS Drafts 5 (http://getdrafts.com/)
    @@ -73,15 +73,17 @@ const Drafts = () => {
    ) : Nothing();

    return {
    draft: {

    },
    editor: {
    // Get the full text currently loaded in the editor.
    // getText :: () -> String
    getText: function() {
    return bindMay(
    getText: () =>
    bindMay(
    mbWin,
    w => Just(w.text())
    ).Just || ''
    },
    ).Just || '',

    // Replace the contents of the editor with a string.
    // setText :: String -> IO ()
  3. RobTrew revised this gist May 20, 2018. 2 changed files with 201 additions and 244 deletions.
    201 changes: 201 additions & 0 deletions BBDrafts.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,201 @@
    // A draft JXA library which aims to provide macOS BBEdit
    // (https://www.barebones.com/products/bbedit/)
    //
    // with some of the iOS Drafts 5 (http://getdrafts.com/)
    // editor functions.

    // Save this file as '~/Library/Script Libraries/BBDrafts.js'

    // Rob Trew (c) 2018

    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    // OTHER DEALINGS IN THE SOFTWARE.

    // Ver 0.09

    const Drafts = () => {
    const bb = Application('BBEdit');

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

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

    // Just :: a -> Just a
    const Just = x => ({
    type: 'Maybe',
    Nothing: false,
    Just: x
    });

    // Nothing :: () -> Nothing
    const Nothing = () => ({
    type: 'Maybe',
    Nothing: true,
    });

    // bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
    const bindMay = (mb, mf) =>
    mb.Nothing ? mb : mf(mb.Just);

    // JXA --- (for a case where we invoke some Applescript)

    // evalASLR :: String -> Either String a
    const evalASLR = s => {
    const
    error = $(),
    result = $.NSAppleScript.alloc.initWithSource(s)
    .executeAndReturnError(error),
    e = ObjC.deepUnwrap(error);
    return e ? (
    Left(e.NSAppleScriptErrorBriefMessage)
    ) : Right(ObjC.unwrap(result.stringValue));
    };

    // References shared by functions -----------------
    const
    ws = bb.windows,
    mbWin = ws.length > 0 ? (
    Just(ws.at(0))
    ) : Nothing();

    return {
    editor: {
    // Get the full text currently loaded in the editor.
    // getText :: () -> String
    getText: function() {
    return bindMay(
    mbWin,
    w => Just(w.text())
    ).Just || ''
    },

    // Replace the contents of the editor with a string.
    // setText :: String -> IO ()
    setText: s =>
    bindMay(
    mbWin,
    w => w.text = s
    ),

    // Get text of range that was last selected
    // getSelectedText :: () -> String
    getSelectedText: () =>
    bindMay(
    mbWin,
    w => Just(w.selection.contents())
    ).Just || '',

    // Replace the contents of the last text selection
    // with a string.
    // setSelectedText :: String -> IO ()
    setSelectedText: s =>
    bindMay(
    mbWin,
    w => w.selection.contents = s
    ),

    // Get the last selected range in the editor.
    // Returns an array with the start location of the range
    // and the length of the selection.
    // getSelectedRange :: () -> (Int, Int)
    getSelectedRange: () =>
    bindMay(
    mbWin,
    w => {
    const seln = w.selection;
    return Just([
    seln.characteroffset() - 1,
    seln.length()
    ]);
    }
    ).Just || undefined,

    // Get the current selected text range extended to the
    // beginning and end of the lines it encompasses.
    // getSelectedLineRange :: () -> (Int, Int)
    getSelectedLineRange: () =>
    bindMay(
    mbWin,
    w => {
    const
    seln = w.selection,
    intStart = w.text.lines.at(
    seln.startline() - 1
    ).characteroffset() - 1,
    toLine = w.text.lines.at(
    seln.endline() - 1
    );

    return Just([
    intStart,
    toLine.characteroffset() +
    (toLine.length() - intStart)
    ]);
    }
    ).Just || undefined,

    // Update the text selection in the editor by passing the
    // start location and the length of the new selection.
    // setSelectedRange :: Int -> Int -> IO ()
    setSelectedRange: (intFrom, intLength) =>
    bindMay(
    mbWin,
    w => (
    // Dialling out to AS ( haven't yet found
    // the right selection reference incantation
    // for the JXA Automation object ).
    evalASLR([
    'tell application "BBEdit" to ',
    'tell front window to select ',
    `(characters ${intFrom + 1} thru `,
    `${intFrom + intLength})`
    ].join('')), [
    intFrom, intLength
    ]
    )
    ),

    // Get the substring in a range from the text in the editor.
    // getTextInRange :: Int -> Int -> String
    getTextInRange: (intFrom, intLength) =>
    bindMay(
    mbWin,
    w => Just(w.text().slice(
    intFrom,
    intFrom + intLength
    ))
    ).Just || '',

    // Replace the text in the given range with new text.
    // setTextInRange :: Int -> Int -> String -> IO ()
    setTextInRange: (intFrom, intLength, s) =>
    bindMay(
    mbWin,
    w => {
    const t = w.text();
    return (
    w.text = t.slice(0, intFrom) + s +
    t.slice(intFrom + intLength),
    Just(s)
    )
    }
    ).Just || ''
    }
    };
    };

    const editor = Drafts().editor;
    244 changes: 0 additions & 244 deletions bbEditDrafts5API-008.js
    Original file line number Diff line number Diff line change
    @@ -1,244 +0,0 @@
    (() => {
    'use strict';

    // Set of functions imitating some of the JS editor API of
    // Drafts 5 ( See http://getdrafts.com/ )
    // for experimental use with the macOS JXA interface to BBEDIT

    // Rob Trew (c) 2018

    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    // OTHER DEALINGS IN THE SOFTWARE.

    // Ver 0.08

    // **Pre** Alpha first draft - experiment only with dummy data,
    // and please send bug reports to Twitter @complexpoint

    // USAGE
    /*
    1. Include the whole of the drafts() function below
    in your JavaScript for Automation source text.
    2. Obtain a reference to the API with an incantation like:
    `const editor = drafts().editor;`
    3. Test the functions by writing expressions like:
    `const strText = editor.getText();`
    */


    // MAIN ---------------------------------------------------

    // drafts :: () -> Drafts
    function drafts() {
    const bb = Application('BBEdit');

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

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

    // Just :: a -> Just a
    const Just = x => ({
    type: 'Maybe',
    Nothing: false,
    Just: x
    });

    // Nothing :: () -> Nothing
    const Nothing = () => ({
    type: 'Maybe',
    Nothing: true,
    });

    // bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
    const bindMay = (mb, mf) =>
    mb.Nothing ? mb : mf(mb.Just);

    // JXA --- (for a case where we invoke some Applescript)

    // evalASLR :: String -> Either String a
    const evalASLR = s => {
    const
    error = $(),
    result = $.NSAppleScript.alloc.initWithSource(s)
    .executeAndReturnError(error),
    e = ObjC.deepUnwrap(error);
    return e ? (
    Left(e.NSAppleScriptErrorBriefMessage)
    ) : Right(ObjC.unwrap(result.stringValue));
    };

    // References shared by functions -----------------
    const
    ws = bb.windows,
    mbWin = ws.length > 0 ? (
    Just(ws.at(0))
    ) : Nothing();

    return {
    editor: {
    // Get the full text currently loaded in the editor.
    // getText :: () -> String
    getText: () =>
    bindMay(
    mbWin,
    w => Just(w.text())
    ).Just || '',

    // Replace the contents of the editor with a string.
    // setText :: String -> IO ()
    setText: s =>
    bindMay(
    mbWin,
    w => w.text = s
    ),

    // Get text of range that was last selected
    // getSelectedText :: () -> String
    getSelectedText: () =>
    bindMay(
    mbWin,
    w => Just(w.selection.contents())
    ).Just || '',

    // Replace the contents of the last text selection
    // with a string.
    // setSelectedText :: String -> IO ()
    setSelectedText: s =>
    bindMay(
    mbWin,
    w => w.selection.contents = s
    ),

    // Get the last selected range in the editor.
    // Returns an array with the start location of the range
    // and the length of the selection.
    // getSelectedRange :: () -> (Int, Int)
    getSelectedRange: () =>
    bindMay(
    mbWin,
    w => {
    const seln = w.selection;
    return Just([
    seln.characteroffset() - 1,
    seln.length()
    ]);
    }
    ).Just || undefined,

    // Get the current selected text range extended to the
    // beginning and end of the lines it encompasses.
    // getSelectedLineRange :: () -> (Int, Int)
    getSelectedLineRange: () =>
    bindMay(
    mbWin,
    w => {
    const
    seln = w.selection,
    intStart = w.text.lines.at(
    seln.startline() - 1
    ).characteroffset() - 1,
    toLine = w.text.lines.at(
    seln.endline() - 1
    );

    return Just([
    intStart,
    (toLine.characteroffset() - 1) +
    (toLine.length() - intStart)
    ]);
    }
    ).Just || undefined,

    // Update the text selection in the editor by passing the
    // start location and the length of the new selection.
    // setSelectedRange :: Int -> Int -> IO ()
    setSelectedRange: (intFrom, intLength) =>
    bindMay(
    mbWin,
    w => (
    // Dialling out to AS ( haven't yet found
    // the right selection reference incantation
    // for the JXA Automation object ).
    evalASLR([
    'tell application "BBEdit" to ',
    'tell front window to select ',
    `(characters ${intFrom + 1} thru `,
    `${intFrom + intLength})`
    ].join('')), [
    intFrom, intLength
    ]
    )
    ),

    // Get the substring in a range from the text in the editor.
    // getTextInRange :: Int -> Int -> String
    getTextInRange: (intFrom, intLength) =>
    bindMay(
    mbWin,
    w => Just(w.text().slice(
    intFrom,
    intFrom + intLength
    ))
    ).Just || '',

    // Replace the text in the given range with new text.
    // setTextInRange :: Int -> Int -> String -> IO ()
    setTextInRange: (intFrom, intLength, s) =>
    bindMay(
    mbWin,
    w => {
    const t = w.text();
    return (
    w.text = t.slice(0, intFrom) + s +
    t.slice(intFrom + intLength, -1),
    Just(s)
    )
    }
    ).Just || ''
    }
    };
    };

    // TESTING FUNCTIONS ----------------------------------

    // showJSON :: a -> String
    const showJSON = x => JSON.stringify(x, null, 2);

    // showLog :: a -> IO ()
    const showLog = (...args) =>
    console.log(
    args
    .map(JSON.stringify)
    .join(' -> ')
    );

    // SAMPLE CODE ----------------------------------------

    const e = drafts().editor;

    return showJSON(
    e.getText()
    //e.setTextInRange(1,27,'WWW')
    //e.getTextInRange(1,27)
    //e.setSelectedRange(1, 3)
    //e.getSelectedLineRange()
    //e.getSelectedRange()
    //e.getSelectedText()
    //e.setText(e.getText())
    // editor.setTextInRange(1, 5, "ABRACADABRA")
    );
    })();
  4. RobTrew renamed this gist May 16, 2018. 1 changed file with 1 addition and 11 deletions.
    12 changes: 1 addition & 11 deletions bbEditDrafts5API-007.js → bbEditDrafts5API-008.js
    Original file line number Diff line number Diff line change
    @@ -16,7 +16,7 @@
    // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    // OTHER DEALINGS IN THE SOFTWARE.

    // Ver 0.07
    // Ver 0.08

    // **Pre** Alpha first draft - experiment only with dummy data,
    // and please send bug reports to Twitter @complexpoint
    @@ -81,14 +81,6 @@
    ) : Right(ObjC.unwrap(result.stringValue));
    };

    // showLog :: a -> IO ()
    const showLog = (...args) =>
    console.log(
    args
    .map(x => JSON.stringify(x, null, 2))
    .join(' -> ')
    );

    // References shared by functions -----------------
    const
    ws = bb.windows,
    @@ -163,8 +155,6 @@
    seln.endline() - 1
    );

    showLog('seln.startline()', seln.startline());
    showLog('seln.endline()', seln.endline());
    return Just([
    intStart,
    (toLine.characteroffset() - 1) +
  5. RobTrew renamed this gist May 16, 2018. 1 changed file with 6 additions and 3 deletions.
    9 changes: 6 additions & 3 deletions bbEditDrafts5API-005.js → bbEditDrafts5API-007.js
    Original file line number Diff line number Diff line change
    @@ -16,7 +16,7 @@
    // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    // OTHER DEALINGS IN THE SOFTWARE.

    // Ver 0.05
    // Ver 0.07

    // **Pre** Alpha first draft - experiment only with dummy data,
    // and please send bug reports to Twitter @complexpoint
    @@ -158,13 +158,16 @@
    seln = w.selection,
    intStart = w.text.lines.at(
    seln.startline() - 1
    ).characteroffset(),
    ).characteroffset() - 1,
    toLine = w.text.lines.at(
    seln.endline() - 1
    );

    showLog('seln.startline()', seln.startline());
    showLog('seln.endline()', seln.endline());
    return Just([
    intStart,
    toLine.characteroffset() +
    (toLine.characteroffset() - 1) +
    (toLine.length() - intStart)
    ]);
    }
  6. RobTrew renamed this gist May 15, 2018. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  7. RobTrew revised this gist May 15, 2018. 1 changed file with 20 additions and 9 deletions.
    29 changes: 20 additions & 9 deletions bbEditDrafts5API-004.js
    Original file line number Diff line number Diff line change
    @@ -16,7 +16,7 @@
    // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    // OTHER DEALINGS IN THE SOFTWARE.

    // Ver 0.04
    // Ver 0.05

    // **Pre** Alpha first draft - experiment only with dummy data,
    // and please send bug reports to Twitter @complexpoint
    @@ -25,13 +25,9 @@
    /*
    1. Include the whole of the drafts() function below
    in your JavaScript for Automation source text.
    2. Obtain a reference to the API with an incantation like:
    `const editor = drafts().editor;`
    3. Test the functions by writing expressions like:
    `const strText = editor.getText();`
    */

    @@ -85,6 +81,13 @@
    ) : Right(ObjC.unwrap(result.stringValue));
    };

    // showLog :: a -> IO ()
    const showLog = (...args) =>
    console.log(
    args
    .map(x => JSON.stringify(x, null, 2))
    .join(' -> ')
    );

    // References shared by functions -----------------
    const
    @@ -151,10 +154,18 @@
    bindMay(
    mbWin,
    w => {
    const seln = w.selection;
    const
    seln = w.selection,
    intStart = w.text.lines.at(
    seln.startline() - 1
    ).characteroffset(),
    toLine = w.text.lines.at(
    seln.endline() - 1
    );
    return Just([
    seln.characteroffset() - seln.startcolumn(),
    w.text.lines.at(seln.startline() - 1).length()
    intStart,
    toLine.characteroffset() +
    (toLine.length() - intStart)
    ]);
    }
    ).Just || undefined,
    @@ -237,4 +248,4 @@
    //e.setText(e.getText())
    // editor.setTextInRange(1, 5, "ABRACADABRA")
    );
    })();
    })();
  8. RobTrew revised this gist May 15, 2018. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions bbEditDrafts5API-004.js
    Original file line number Diff line number Diff line change
    @@ -227,10 +227,10 @@
    const e = drafts().editor;

    return showJSON(
    // e.getText()
    e.getText()
    //e.setTextInRange(1,27,'WWW')
    //e.getTextInRange(1,27)
    e.setSelectedRange(1,3)
    //e.setSelectedRange(1, 3)
    //e.getSelectedLineRange()
    //e.getSelectedRange()
    //e.getSelectedText()
  9. RobTrew created this gist May 15, 2018.
    240 changes: 240 additions & 0 deletions bbEditDrafts5API-004.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,240 @@
    (() => {
    'use strict';

    // Set of functions imitating some of the JS editor API of
    // Drafts 5 ( See http://getdrafts.com/ )
    // for experimental use with the macOS JXA interface to BBEDIT

    // Rob Trew (c) 2018

    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    // OTHER DEALINGS IN THE SOFTWARE.

    // Ver 0.04

    // **Pre** Alpha first draft - experiment only with dummy data,
    // and please send bug reports to Twitter @complexpoint

    // USAGE
    /*
    1. Include the whole of the drafts() function below
    in your JavaScript for Automation source text.
    2. Obtain a reference to the API with an incantation like:
    `const editor = drafts().editor;`
    3. Test the functions by writing expressions like:
    `const strText = editor.getText();`
    */


    // MAIN ---------------------------------------------------

    // drafts :: () -> Drafts
    function drafts() {
    const bb = Application('BBEdit');

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

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

    // Just :: a -> Just a
    const Just = x => ({
    type: 'Maybe',
    Nothing: false,
    Just: x
    });

    // Nothing :: () -> Nothing
    const Nothing = () => ({
    type: 'Maybe',
    Nothing: true,
    });

    // bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
    const bindMay = (mb, mf) =>
    mb.Nothing ? mb : mf(mb.Just);

    // JXA --- (for a case where we invoke some Applescript)

    // evalASLR :: String -> Either String a
    const evalASLR = s => {
    const
    error = $(),
    result = $.NSAppleScript.alloc.initWithSource(s)
    .executeAndReturnError(error),
    e = ObjC.deepUnwrap(error);
    return e ? (
    Left(e.NSAppleScriptErrorBriefMessage)
    ) : Right(ObjC.unwrap(result.stringValue));
    };


    // References shared by functions -----------------
    const
    ws = bb.windows,
    mbWin = ws.length > 0 ? (
    Just(ws.at(0))
    ) : Nothing();

    return {
    editor: {
    // Get the full text currently loaded in the editor.
    // getText :: () -> String
    getText: () =>
    bindMay(
    mbWin,
    w => Just(w.text())
    ).Just || '',

    // Replace the contents of the editor with a string.
    // setText :: String -> IO ()
    setText: s =>
    bindMay(
    mbWin,
    w => w.text = s
    ),

    // Get text of range that was last selected
    // getSelectedText :: () -> String
    getSelectedText: () =>
    bindMay(
    mbWin,
    w => Just(w.selection.contents())
    ).Just || '',

    // Replace the contents of the last text selection
    // with a string.
    // setSelectedText :: String -> IO ()
    setSelectedText: s =>
    bindMay(
    mbWin,
    w => w.selection.contents = s
    ),

    // Get the last selected range in the editor.
    // Returns an array with the start location of the range
    // and the length of the selection.
    // getSelectedRange :: () -> (Int, Int)
    getSelectedRange: () =>
    bindMay(
    mbWin,
    w => {
    const seln = w.selection;
    return Just([
    seln.characteroffset() - 1,
    seln.length()
    ]);
    }
    ).Just || undefined,

    // Get the current selected text range extended to the
    // beginning and end of the lines it encompasses.
    // getSelectedLineRange :: () -> (Int, Int)
    getSelectedLineRange: () =>
    bindMay(
    mbWin,
    w => {
    const seln = w.selection;
    return Just([
    seln.characteroffset() - seln.startcolumn(),
    w.text.lines.at(seln.startline() - 1).length()
    ]);
    }
    ).Just || undefined,

    // Update the text selection in the editor by passing the
    // start location and the length of the new selection.
    // setSelectedRange :: Int -> Int -> IO ()
    setSelectedRange: (intFrom, intLength) =>
    bindMay(
    mbWin,
    w => (
    // Dialling out to AS ( haven't yet found
    // the right selection reference incantation
    // for the JXA Automation object ).
    evalASLR([
    'tell application "BBEdit" to ',
    'tell front window to select ',
    `(characters ${intFrom + 1} thru `,
    `${intFrom + intLength})`
    ].join('')), [
    intFrom, intLength
    ]
    )
    ),

    // Get the substring in a range from the text in the editor.
    // getTextInRange :: Int -> Int -> String
    getTextInRange: (intFrom, intLength) =>
    bindMay(
    mbWin,
    w => Just(w.text().slice(
    intFrom,
    intFrom + intLength
    ))
    ).Just || '',

    // Replace the text in the given range with new text.
    // setTextInRange :: Int -> Int -> String -> IO ()
    setTextInRange: (intFrom, intLength, s) =>
    bindMay(
    mbWin,
    w => {
    const t = w.text();
    return (
    w.text = t.slice(0, intFrom) + s +
    t.slice(intFrom + intLength, -1),
    Just(s)
    )
    }
    ).Just || ''
    }
    };
    };

    // TESTING FUNCTIONS ----------------------------------

    // showJSON :: a -> String
    const showJSON = x => JSON.stringify(x, null, 2);

    // showLog :: a -> IO ()
    const showLog = (...args) =>
    console.log(
    args
    .map(JSON.stringify)
    .join(' -> ')
    );

    // SAMPLE CODE ----------------------------------------

    const e = drafts().editor;

    return showJSON(
    // e.getText()
    //e.setTextInRange(1,27,'WWW')
    //e.getTextInRange(1,27)
    e.setSelectedRange(1,3)
    //e.getSelectedLineRange()
    //e.getSelectedRange()
    //e.getSelectedText()
    //e.setText(e.getText())
    // editor.setTextInRange(1, 5, "ABRACADABRA")
    );
    })();