If we use highlight format to mark a focal row in a list,
we may at some point want to move that highlight on the next row
(clearing it from the current row).
Here is a script which cycles highlighting through the set of sibling rows which contains the selection cursor.
Expand disclosure triangle to view JS source
(() => {
"use strict";
// -------- MOVE HIGHLIGHT FORMAT TO NEXT ROW --------
// Highlighting cycled through the peer rows which
// contain the selection cursor.
// The highlighting advances each time the script is run,
// and cycles back to the first sibling afer the last.
// Rob Trew @2024
// Ver 0.2
// main :: IO ()
const main = () => {
const
frontDoc = Application("Bike")
.documents.at(0);
return either(
alert("Highlight cycled among siblings.")
)(
focalName => focalName
)(
bindLR(
frontDoc.exists()
? Right(frontDoc.selectionRow())
: Left("No document open in Bike.")
)(
highlightCycledLR
)
);
};
// highlightCycledLR :: Bike Row -> Either String String
const highlightCycledLR = selnRow => {
// Either a message, or the name of a row which
// has newly received the highlighting focus
// in a cycle among the set of peer rows which
// contains the selection.
const parentRow = selnRow.containerRow;
return bindLR(
nextMarkedSiblingLR(parentRow.rows)
)(
nextRow => moveHighlightToChildByIdLR(
parentRow
)(
nextRow.id()
)
);
};
// ------------- HIGHLIGHTING FUNCTIONS --------------
// hasMarkedText :: Row -> Bool
const hasMarkedText = row =>
// True only if any attribute run in the row
// is highlighted.
0 < row.textContent.attributeRuns.where({
highlight: true
})
.length;
// highlightClearedOrSet :: String -> Row -> IO [String]
const highlightClearedOrSet = focalId =>
// Highlight set in the row if it has the focal ID,
// otherwise any highlighting cleared from it.
row => {
const
isMarked = focalId === row.id(),
runs = row.textContent.attributeRuns;
return (
enumFromTo(0)(runs.length - 1)
.forEach(
i => runs.at(i).highlight = isMarked
),
isMarked
? [row.name()]
: []
);
};
// moveHighlightToChildByIdLR :: Row ->
// String -> Either String String
const moveHighlightToChildByIdLR = parentRow =>
// Either a message or the name of the row
// which matches the given id, and has now been
// highlit, with any highlighting cleared from
// its peers.
focalChildId => {
const
markedNames = parentRow.rows.where({
_not: [{name: {_beginsWith: "="}}]
})()
.flatMap(
highlightClearedOrSet(focalChildId)
);
return 0 < markedNames.length
? Right(markedNames[0])
: Left(
[
"Child not found for highlighting",
`by id: "${focalChildId}"`
]
.join("\n")
);
};
// nextMarkedSiblingLR :: Rows ->
// Either String Row
const nextMarkedSiblingLR = peerRows => {
// Either a message or the next peer row
// that in the highlighting cycle.
// (The first peer if the last is highlit).
const
n = peerRows.length,
i = peerRows().findIndex(hasMarkedText);
return 0 < n
? Right(
peerRows.at(
-1 !== i
? (1 + i) % n
: 0
)
)
: Left("Empty collection of peers.");
};
// ----------------------- JXA -----------------------
// alert :: String => String -> IO String
const alert = title =>
s => {
const sa = Object.assign(
Application("System Events"), {
includeStandardAdditions: true
});
return (
sa.activate(),
sa.displayDialog(s, {
withTitle: title,
buttons: ["OK"],
defaultButton: "OK"
}),
s
);
};
// --------------------- GENERIC ---------------------
// Left :: a -> Either a b
const Left = x => ({
type: "Either",
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: "Either",
Right: x
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = lr =>
// Bind operator for the Either option type.
// If lr has a Left value then lr unchanged,
// otherwise the function mf applied to the
// Right value in lr.
mf => "Left" in lr
? lr
: mf(lr.Right);
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
// Application of the function fl to the
// contents of any Left value in e, or
// the application of fr to its Right value.
fr => e => "Left" in e
? fl(e.Left)
: fr(e.Right);
// enumFromTo :: Int -> Int -> [Int]
const enumFromTo = m =>
// Enumeration of the integers from m to n.
n => Array.from(
{length: 1 + n - m},
(_, i) => m + i
);
// MAIN ---
return main();
})();
To test in Script Editor, set the language selector at top left to JavaScript
(rather than AppleScript
).
See: Using Scripts | Bike