Here’s a draft of:
// First not @done selected in project containing cursor
// or first not @done in document, if project complete.
Expand disclosure triangle to view JS source
(() => {
"use strict";
// Rob Trew @2021, 2024
// First not @done selected in project containing cursor
// or first not @done in document, if project complete.
// Ver 2.07
const enclosingProject = "ancestor-or-self::project[-1]";
const firstNotDone = "//task not @done[0]";
// ------------------- JXA CONTEXT -------------------
// main :: IO ()
const main = () => {
const doc = Application("TaskPaper").documents.at(0);
return doc.exists()
? doc.evaluate({
script: `${TaskPaperContext}`,
withOptions: {
enclosingProject,
firstNotDone
}
})
: "No documents open in TaskPaper";
};
// ---------------- TASKPAPER CONTEXT ----------------
// TaskPaperContext :: Editor -> IO ()
const TaskPaperContext = (editor, options) => {
const
outline = editor.outline,
{ enclosingProject, firstNotDone } = options,
match = outline.evaluateItemPath(
enclosingProject + firstNotDone,
editor.selection.startItem
)[0] || outline.evaluateItemPath(
firstNotDone
)[0];
return match
? (
editor.moveSelectionToItems(
match, match.bodyString.length
),
`${match.bodyContentString}`
)
: "No remaining tasks in document.";
};
// MAIN ---
return main();
})();
1 Like
Jim
November 22, 2024, 12:24am
22
The weekend arrives early in your timezone!
Colour me impressed! The code is brilliant, and I will have to put time into studying it.
I hereby dub you: Honorable Knight of JavaScript!
The work is all Jesse’s
( I had just been slow to grasp the use of that contextItem argument )
1 Like
complexpoint:
The work is all Jesse’s
I’ll respectfully disagree with that, but thanks
2 Likes
Jim
November 22, 2024, 4:51pm
25
Regardless of the specifics, both of you are wonderful!
1 Like
Jim
February 7, 2026, 11:57pm
26
Can the above script be adjusted so that the cursor is placed at the end of the last task (as opposed to the first one) that is not @done ? Preferably only including the current project and its ancestors.
Example:
Project One:
Task 1A @done (2026-02-07)
Task 1B
Task 1C ← (to here)
Project Two:@done (2026-02-07)
Task 2A @done (2026-02-07)
Task 2A@done(2026-02-07)
Task 2A@done(2026-02-07)| ← (cursor goes from here)
Jim
February 8, 2026, 12:02am
27
Experimenting with:
const firstNotDone = "//task not @done[-1]";
1 Like
( In a southern hemisphere airport without macOS - hope to be back at a desk c. Tuesday
1 Like
Re
Preferably only including the current project and its ancestors.
I may be misunderstanding that formulation – it looks in your from → to example, as if the from is in the current project but the to is in a preceding (rather than ancestral or identical) project.
i.e. if nothing is found in the current project or its ancestors, we still continue back upwards ?
( Forgive me if I’m stumbling a bit – feet already in Europe, but soul only partially arrived )
1 Like
Jim
February 12, 2026, 12:34pm
31
I didn’t explain it well. Your soul may have arrived in the midst of a poorly written scope
Yes, on:
if nothing is found in the current project or its ancestors, we still continue back upwards ?
All the way up to the end of the first undone task or project in the entire document.
1 Like
The only part of that example that seems unclear is the question of whether project names are visited or not.
(The example appears to skip the latter Project Two: – jumping from the bottom,
but visits, I think, the earlier Project Two:)
In the meanwhile, here’s a first rough draft of something:
Expand disclosure triangle to view JS source
(() => {
"use strict";
// Selection moved to:
// First incomplete non-empty row before current selection
// Rob Trew @2026
// Ver 0.03
const
neitherDoneNorBlank = '(not @done and @text != "")',
precedingNotDone = `preceding::${neitherDoneNorBlank}`;
// ------------------- JXA CONTEXT -------------------
// main :: IO ()
const main = () => {
const doc = Application("TaskPaper").documents.at(0);
return doc.exists()
? doc.evaluate({
script: `${TaskPaperContext}`,
withOptions: { precedingNotDone }
})
: "No documents open in TaskPaper";
};
// ---------------- TASKPAPER CONTEXT ----------------
// TaskPaperContext :: Editor -> IO ()
const TaskPaperContext = (editor, { precedingNotDone }) => {
const
matches = editor.outline.evaluateItemPath(
`${precedingNotDone}[-1]`,
editor.selection.startItem
),
match = matches && (0 < matches.length)
? matches[0]
: null;
return match
? (
editor.moveSelectionToItems(
match, match.bodyString.length
),
match.bodyContentString
)
: "No remaining incomplete tasks in document.";
};
// MAIN ---
return main();
})();
Let me know where it diverges from what you have in mind.
(and have a good weekend)
Jim
February 14, 2026, 3:56pm
34
The only part of that example that seems unclear is the question of whether project names are visited or not.
This will teach me to make up examples late at night—my apologies!
In the last example, the project should have should be marked as done:
Project Two: @done (2026-02-14)
•••
It looks like your rough draft does exactly what I want.
Your skill at interpreting my bad examples is amazing. Kudos!
Jim
February 14, 2026, 4:27pm
35
I just used the script during my weekly review and it worked flawlessly.
Thank you again for your help! May your weekend be great!
1 Like