Is there a way to get Bike to give me the word count only for selected text or branches?
There isn’t right now. It makes sense as a feature that I should add, just hasn’t made it to top of priority list yet.
There is this script:
Which could be modified to get the counts you want until I build the feature in.
In the simplest case of directly selected text, it should suffice, I think, to write:
(() => {
"use strict";
const doc = Application("Bike").documents.at(0);
return doc.exists() ? (
doc.selectedText().split(/\s+/gu).length
) : "No document open in Bike.";
})();
but could you expand a little on selected branches ?
When a collapsed row is selected, are you looking for:
- a count only of the directly selected words in that row ?
- a count of words in that row and all its descendants ?
- something slightly different ?
If a selected row is not collapsed but has descendants which are, do you want:
- A full count of the words in that row and all its descendants ?
- A count of the words in the row and all visible descendants ?
- Something else ?
We could, for example write two scripts:
- Count of words in selected rows and all their descendants.
- Count of words in selected rows and visible descendants.
My use case is composing text from a starting outline. Typically, i’m trying to get my text below some limit, so i would prefer the word count to err on the side of including too much rather than including too little (because i hid something). Thus, i’d like to have the word count of selected rows and all their descendants (including hidden ones).
Well, here’s a couple of drafts to experiment with.
Imagine we have selected Nu
and Rho
below
both of the branch word count versions below should return a word count of 7 (selected rows and descendants thereof)
but if we now collapse Nu
then
- the first script should still return 7
- while the second script (ignoring hidden lines) should now return 4
Expand disclosure triangle to view JS source (Branch Word Count)
(() => {
"use strict";
// Rob Trew @2023
// Count of words in selected Bike branches,
// including rows hidden by folding.
// Ver 0.1
const doc = Application("Bike").documents.at(0);
return doc.exists() ? (() => {
const
rows = doc.rows,
selnParents = rows.where({
containsRows: true,
selected: true
});
// Ids of selected rows and descendants,
return Array.from(
new Set([
...selnParents.id(),
...selnParents.entireContents().flatMap(
xs => xs.map(x => x.id())
),
...rows.where({
containsRows: false,
selected: true
}).id()
])
)
// with texts joined together and counted.
.map(x => rows.byId(x).name())
.join(" ")
.split(/\s+/gu)
.length;
})() : "No document open in Bike.";
})();
Expand disclosure triangle to view variant skipping hidden descendants
(() => {
"use strict";
// Rob Trew @2023
// Count of words in selected Bike branches,
// ignoring rows hidden by folding.
// Ver 0.1
const doc = Application("Bike").documents.at(0);
return doc.exists() ? (() => {
const
rows = doc.rows,
selnParents = rows.where({
containsRows: true,
selected: true
});
// Ids of selected rows and descendants,
return Array.from(
new Set([
...selnParents.id(),
...selnParents.entireContents().flatMap(
xs => xs.map(x => x.id())
),
...rows.where({
containsRows: false,
selected: true
}).id()
])
)
// except any invisible rows,
.flatMap(x => {
const row = rows.byId(x);
return row.visible()
? [row.name()]
: [];
})
// with texts joined together and counted.
.join(" ")
.split(/\s+/gu)
.length;
})() : "No document open in Bike.";
})();
To test in Script Editor, set language selector at top left to JavaScript
.
See: Using Scripts - Bike
Just drafts – it’s possible that there are more efficient ways of writing them which haven’t occurred to me this morning
that’s pretty cool. thank you.
in fact, being able to word-count folded text means that i only have to have my cursor in a parent, instead of having to select text. very convenient.
I’m a newbie with applescript, so I’m running it within apple script editor. (I added it to the Apple script menu, but i can’t figure out how i would see the output word count).
I tend to use Keyboard Maestro or FastScripts for the display and key-binding side, but here’s a combined version which aims to:
- display both counts in a dialog
- and also copy the counts to the clipboard.
Expand disclosure triangle to view JS source
(() => {
"use strict";
// Rob Trew @2023
// Count of words in selected Bike branches,
// both with and without rows hidden by folding.
// Report displayed and copied to clipboard.
// Ver 0.3
// eslint-disable-next-line max-lines-per-function
const main = () => {
const doc = Application("Bike").documents.at(0);
return doc.exists() ? (() => {
const
rows = doc.rows,
selnParents = rows.where({
containsRows: true,
selected: true
}),
// Selected rows and descendants,
// visible and hidden.
[nVisible, nHidden] = partition(
row => row.visible()
)(
Array.from(
new Set([
...selnParents.id(),
...selnParents.entireContents()
.flatMap(
xs => xs.map(x => x.id())
),
...rows.where({
containsRows: false,
selected: true
}).id()
])
)
.map(x => rows.byId(x))
)
.map(
xs => xs.map(row => row.name())
.join(" ")
.split(/\s+/ug)
.length
),
nAll = nVisible + nHidden,
report = [
`Words in selected branches: ${nAll}`,
`Excluding hidden rows: ${nVisible}`
]
.join("\n");
const
sa = Object.assign(
Application.currentApplication(),
{includeStandardAdditions: true}
);
return (
sa.setTheClipboardTo(report),
sa.activate(),
sa.displayDialog(report, {
withTitle: "Words in selected Bike branches",
buttons: ["OK"],
defaultButton: "OK"
}),
report
);
})() : "No document open in Bike.";
};
// --------------------- GENERIC ---------------------
// first :: (a -> b) -> ((a, c) -> (b, c))
const first = f =>
// A simple function lifted to one which applies
// to a tuple, transforming only its first item.
xy => [f(xy[0]), xy[1]];
// partition :: (a -> Bool) -> [a] -> ([a], [a])
const partition = p =>
// A tuple of two lists - those elements in
// xs which match p, and those which do not.
xs => [...xs].reduce(
(a, x) => (
p(x) ? (
first
) : second
)(ys => [...ys, x])(a),
[[], []]
);
// second :: (a -> b) -> ((c, a) -> (c, b))
const second = f =>
// A function over a simple value lifted
// to a function over a tuple.
// f (a, b) -> (a, f(b))
xy => [xy[0], f(xy[1])];
// MAIN
return main();
})();
works great! thanks very much.
After a bit of use, i ended up commenting out the line copying the results to clipboard:
// sa.setTheClipboardTo(report)