Skip to content

Instantly share code, notes, and snippets.

@RobTrew
Last active January 17, 2023 16:45
Show Gist options
  • Save RobTrew/675b0f14f87b77ee025755e067022c62 to your computer and use it in GitHub Desktop.
Save RobTrew/675b0f14f87b77ee025755e067022c62 to your computer and use it in GitHub Desktop.
First draft of (Drafts 5 - based) JS (JXA) interface functions for 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/)
// 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.10
const Drafts = () => {
const
bb = Application('BBEdit'),
sa = Object.assign(Application.currentApplication(), {
includeStandardAdditions: true
});
// 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 {
app: {
getClipboard: () => sa.theClipboard(),
setClipboard: s => sa.setTheClipboardTo(s)
},
draft: {
},
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() +
(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
drafts = Drafts(),
editor = drafts.editor,
app = drafts.app;
@RobTrew
Copy link
Author

RobTrew commented May 15, 2018

Ver 0.5
Fixed a bug in getSelectedLineRange()
(corrected value returned for multi-line selections)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment