Last active
January 17, 2023 16:45
-
-
Save RobTrew/675b0f14f87b77ee025755e067022c62 to your computer and use it in GitHub Desktop.
First draft of (Drafts 5 - based) JS (JXA) interface functions for BBEDIT
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Ver 0.5
Fixed a bug in getSelectedLineRange()
(corrected value returned for multi-line selections)